New version of NS-USBloader released.
What's new? GoldLeaf support. And UI improvements of course. Actually I don't really like GoldLeaf since USB support is weak/unstable for now and have some bugs, so I would strongly recommend get TinFoil and use it for now.
Why v0.2.2? That's because I got some feedback regarding how v0.2 worked. So in v0.2.1 I changed a bit progress bar behaviour and in v0.2.2 fixed some dependency issues that didn't allow using it in macOS. Thanks to reddit users who helped me with this.
So as I learned, the requirements to use Java8+ is not good enough, and actually people have to get 8u60 or higher. It contains missed methods of the JavaFX elements.. and it's wired thing. Oracle decided to 'detach' JavaFX to separate project (openJFX) and in JRE/JDK 11 (for example) java code uses embedded libraries that are self-described and awesome by itself. But it's all just my thoughts.
Sooooooooo, new version. Here you go:
Yeah, new white theme. Disabled by-default but I like it =)
When I looked on GoldLeaf I asked myself 'WHY?' realized, that there is no way to transfer multiple files at once (this project is actively growth so maybe, in future they will add such feature). That's why table appeared. It's sort of limitation to leave ability for multiple files selection and in same time restrict their number for GoldLeaf. Restriction is useless (?) but possible for TinFoil.
Looking on it now, I see a lack of "select all/unselect all" checkbox on column level. Maybe it would be good to add it and set inactive when 'GoldLeaf' selected.
Also, obviously it should be great to add drag-and-drop files ability.. Anyway all these restrictions are non-critical.
Another thing added is settings storage. NS-USBloader can remember user settings such as selected backend application, theme, opened with NSP files. It uses internal Java's feature that bla-bla-bla create files/windows registry entry for application. As I know, in Linux file with settings located at ~/.java/.userPrefs/NS-USBloader/prefs.xml. In windows it's somewhere near HKEY_CURRENT_USER\SOFTWARE\... . No idea where is it on macOS. Somewhere in /Library I guess.
Sooo, NS-USBloader. What is all about and how to use?
First of all, if you don't know what is 'Nintendo Switch' and 'tinfoil' just skip this article.
Otherwise, let's move on!
This application developed to replace tinfoil default python script (because it's python, it doesn't have GUI, I'm noob that script didn't worked for me returning OVERFLOW something-something).
Let's begin already!
First of all, go get JRE (Java runtime environment). And install it. The version you need it 8u60 or higher. If you're on mac, see section below (you will need JDK not JRE).
Oracle JRE (or JDK) is good. OpenJDK is good. Others, most likely, also should be good.
Situation: "I'm Windows user and never used/heard about python script for TinFoil"
Now go get Zadig. Open it.
Take you Switch.
Open TinFoil.
Select 'Title Managment'
Select 'USB Install NSP'
You will see next message (in Switch):
Waiting for USB to be ready...
Plug USB cord to your Switch and PC
You will see next message (in Switch):
Waiting for USB to be ready...
USB is ready. Waiting for header...
Open Zadig.
Click 'Options' → 'List All Devices'
Select in drop-down 'Nintendo Switch'
Select 'libusbK (v3.0.7.0)' in.. Here is the picture
Click 'Install Driver'
In the end driver should be installed.. somehow..
Verify: unplug USB cord and plug it in again. Take a look on 'Zadig'. 'Driver' field should be 'libusbK (v3.0.7.0)' instead of '(NONE)'.
You're good now.
Check next 'Situation'.
Situation: "I'm windows user and I already installed libusbK"
Start NS-USBloader (jar file) as regular application.
(If it doesn't work: open cmd.exe, type: java -jar C:\path\to\NS-USBLoader.jar)
That's all. You're all set.
BTW, do you wanna see screenshots? Scroll down then ( ^-^)
Situation: "I'm on Linux"
No special configuration needed if you're about to start this application from 'root' .
# java -jar /path/to/application/NS-USBloader.jar
If you want to use app as regular user, do next (as root):
# vim /etc/udev/rules.d/99-NintendoSwitch.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="3000", GROUP="plugdev"
# udevadm control --reload-rules && udevadm trigger
Done! Now you can start application from any user who is in the 'plugdev' groups. You should be there, and all linux-human-users should be there.
Also, did you know how to make file association for *.jar files in KDE? For opening them by double-clicking in Dolphin and so on.. Anyway, here is the picture of system settings. Take a look on 'Command field' in 'Application' tab.
Situation: "I'm on Mac"
Double-click on downloaded JAR file and follow instructions to install Java JDK.
When installed and all security options set, just double-click on it, or:
# java -jar /path/to/application/NS-USBloader.jar
If you want to start it by clicking:
# sudo chmod +x filename.jar
Application work illustration
So after you opened tinfoil in 'USB Install NSP' mode, start application, click 'Select .NSP files' button.
Select files (multiple selection allowed)
Click 'Upload to NS'
Take your NS. Select what do you want to install like this:
In application you will see something like this after transfer finishes:
And in Switch:
P.S. BTW, you can't use application without graphical environment. (Tell me in comments if you need such feature.)
Сегодня разберёмся как с Gentoo поставить OpenJDK 11 или любой последней версии, настроить idea на работу с ней. Поставим и настроим JavaFX (openJFX SDK). Прикрутим её к IDEA и обозначим подводные камни при сборке всё-в-одном-jar для десктопного приложения используя Maven.
Итак, в списке доступных без плясок Gentoo Java VM есть icedtea (версии 8, т. е. условно тот самый openJDK 8) и страшно бесящий при обновлении oracle-jdk (его блобы нужно каждый раз качать с офф-сайта oracle и ложить в distfiles). OpenJDK как бы и доступен, но обёртки в виде 'eselect' прописывающие все полезные переменные окружения а-ля $JAVA_HOME спрятаны в USE-флаг 'gentoo-vm', который замаскирован. Если интересны подробности таких стрессов, можно почитать что пишут по этому поводу (даже) в самом ebuild:
$ vim -R /usr/portage/dev-java/openjdk/openjdk-11.0.1_p13.ebuild
C openJFX вообще всё странно. Пакета нет, хотя в багзилле тикету пошел четвёртый год https://bugs.gentoo.org/547918. Intellij Idea традиционно обновляется один-два раза в год.. впрочем это уже другая печальная история со счастливым workaround.
Заходим в idea. Открываем Run → Edit Configuration...
Переходим в Templates → Application
И прописываем для 'VM Options:' --module-path /home/<USERNAME>/javafx-sdk-11.0.1/lib --add-modules=javafx.controls,javafx.fxml
Такое же можно (нужно) прописать для уже существующих записей в 'Application'.
Maven
Для десктоп-приложений можно засунуть всё что касается JavaFX прямо в приложение. Для этого прописываем в POM зависимости:
На счёт необходимости засовывания "javafx-media" я не уверен.
И на этом всё!
p.s.
А теперь немного не по теме. При переходе с 8ки на 11ку (в которой уже давно есть понятие модулей), мой готовый JAR со всеми зависимостями отказался запускаться выдавая:
Error: JavaFX runtime components are missing, and are required to run this application
Тут конечно можно сделать всё нормально, через испоьзование более в новых языковых конструктов (ключевые слова: modules, module-info.java) а можно добавить надстройку над основным классом (который с main() и который наследуется от Application).
package simplepackage;
public class Main {
public static void main(String[] args){ MainFX.main(args); }
}
Где MainFX — класс, наследуемый от Application.
Ну и прописать его в манифесте основным. Для POM выглядит это так:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<manifest>
<mainClass>simplepackage.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
И никаких проблем.
UPD: Как теперь быть с падающим Android-studio?
После перехода на JDK11 у меня начал падать android-studio поставленый из портажей. Код ошибки приводить не буду, но чтобы исправить можно сделать так:
Сначала смотрим что у нас в /usr/lib/jvm/
ls /usr/lib/jvm/
Далее пишем алиас для android-studio:
$ vim ~/.bashrc
alias android-studio="JAVA_HOME=/usr/lib/jvm/icedtea-bin-8 JDK_HOME=/usr/lib/jvm/icedtea-bin-8 JAVAC=/usr/lib/jvm/icedtea-bin-8/bin/javac android-studio"
После этого студия начинает работать как надо. В новом терминале разумеется.
mplayer4anime — это фронт-энд к плееру mplayer. Если ищите удобный способ запускать mkv и mka одновременно — это как раз оно!
Задача проста, заменить bash-скрипты для запуска связок mkv+mka+субтитров. Почему это нужно? Ну, например, чтобы продолжать раздавать торренты в том виде, в котором они были скачаны. Конечно, в интересах удобства сразу в голову придёт идея объединить mkv и mka в один контейнер используя mkvmerge, но тогда не получится посидеть на раздаче или придётся жертвовать свободным местом на диске.
В итоге, этот «агрегатор» даёт возможность без лишних телодвижений загрузить весь сезон аниме в список и не городить очередной велосипед.
GUI, ня!
Системные требования: JRE-1.8 (8) или выше.
Протестировано в Gentoo (virtual/jre 1.8.0-r1) и Debian Stretch (стандартный образ + openjfx).
Лицензия: GNU GPL v3.
Пиктограммы: material design.
Прочая графика: работы находящиеся в общественном достоянии или распространяющиеся по лицензии CC0 + собственные работы.
Версия на данном этапе вполне рабочая, но некоторые экстра-плюшки всё ещё не реализованы, например drag-n-drop.
И ещё, работает всё именно с mplayer а не с mpv или smplayer (хотя хотелось бы), так что подучите команды mplayer (f - полноэкранный режим, enter - закрыть, пробел - пауза).
Ах да, в будующих релизах будет добавлена поддержка Windows и, вероятно, безшовный запуск (отдельно устанавливать javaFX/JRE не потребуется). На macOS должно и так работать. Скажите мне если вдруг решитесь попробовать!
Ооо.. вот ещё, если вам вдруг понравилось, оставьте коммент! А даже если не понравилось, то тоже можно =)
Сегодня мы будем создавать виджет, который может обновлять текст в нём по нажатию на одну из его четырёх кнопок. По нажатию на первые две будет вызываться MainActivity и обновлять текст на тот, что был передан в зависимости от того, какая из двух кнопок нажата. По нажатию на третью и четвёртую текст просто будет обновляться с помощью IntentSerive службы.
Код проекта доступен по ссылке https://github.com/developersu/MyWidgetNoConf/ .
Добавим классы MyIntentService и myWidget в основной package.
java→com.blogspot.developersu.mywidgetnoconf
→MyIntentService
→myWidget
Перейдём собственно к коду. Начнём с описания функционала виджета.
Мы будем использовать onUpdate и onEnabled плюс функцию для определения функций клавиш. Сохранением данных тестовом поле пренебрежем.
Итак, для начала код:
package com.blogspot.developersu.mywidgetnoconf;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
public class myWidget extends AppWidgetProvider {
private void setWidgetIntents(Context context, AppWidgetManager appWidgetManager, int appWidgetIds[]){
RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.my_widget_layout);
for (int appWidgetId : appWidgetIds) {
Intent intentBtn = new Intent(context, MainActivity.class);
intentBtn.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intentBtn.putExtra("signature", "Button 1");
intentBtn.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP );
PendingIntent pi = PendingIntent.getActivity(context, appWidgetId+0, intentBtn, PendingIntent.FLAG_UPDATE_CURRENT);
rv.setOnClickPendingIntent(R.id.widgetBtn, pi);
/*===================================================================*/
// Set the same for button 2
Intent intentBtn2 = new Intent(context, MainActivity.class);
intentBtn2.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intentBtn2.putExtra("signature", "Button 2");
intentBtn2.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP );
PendingIntent pi2 = PendingIntent.getActivity(context, appWidgetId+1, intentBtn2, PendingIntent.FLAG_UPDATE_CURRENT);
rv.setOnClickPendingIntent(R.id.widgetBtn2, pi2);
/*===================================================================*/
Intent intentBtn3 = new Intent(context, MyIntentService.class);
intentBtn3.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intentBtn3.putExtra("signature", "Button 3");
PendingIntent pi3 = PendingIntent.getService(context, appWidgetId+2, intentBtn3, PendingIntent.FLAG_UPDATE_CURRENT);
rv.setOnClickPendingIntent(R.id.widgetBtn3, pi3);
/*===================================================================*/
Intent intentBtn4 = new Intent(context, MyIntentService.class);
intentBtn4.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intentBtn4.putExtra("signature", "Button 4");
PendingIntent pi4 = PendingIntent.getService(context, appWidgetId+3, intentBtn4, PendingIntent.FLAG_UPDATE_CURRENT);
rv.setOnClickPendingIntent(R.id.widgetBtn4, pi4);
appWidgetManager.updateAppWidget(appWidgetId, rv);
}
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
setWidgetIntents(context, appWidgetManager, appWidgetIds);
}
@Override
public void onEnabled(Context context) {
AppWidgetManager awm = AppWidgetManager.getInstance(context);
ComponentName compName = new ComponentName(context, myWidget.class);
int[] widgetIds = awm.getAppWidgetIds(compName); // избыточно, но оставим для наглядности
setWidgetIntents(context, awm, widgetIds);
}
}
У нас есть функция setWidgetIntents которая принимает context, AppWidgetManager и множество ID среди которых есть и ID нашего виджета. onUpdate — все данные у нас сразу имеются, так что просто передаём их в функцию onEnabled — создаём объект AppWidgetManager используя AppWidgetManager.getInstance(context), создаём ComponentName, который нам понадобится для вытаскивания передавая ему context и имя класса нашего виджета. IDs получаем через awm.getAppWidgetIds(compName). setWidgetIntents — опеределим “RemoteViews rv” чтобы через него присваивать кнопкам действия. Далее создадим цикл для всех полученных widget ID.
Присваивать каждой кнопке действия следует используя PendingIntent. Для него требуется Intent в который в первом и втором случае обращён к MainActivitiy.class, а в третьем и четвёртом — к MyIntentService.class.
В каждый такой Intent мы помещаем значение кнопки, которое будет передаваться. Это будет строка с именем “signature” и значением “Button N”, где N номер кнопки. Также передаём в него ID виджета, который инициировал вызов Intent.
Кроме того, в первых двух случаях мы определяем флаг Intent.FLAG_ACTIVITY_SINGLE_TOP который говорит системе, что Activity не должен запускаться, если экземпляр уже существует в стеке. Таким образом, если activity уже запущен, то приниматься Intent с этим флагом будет в методе onNewIntent(). Альтернативой этому может быть установка в манифесте launchMode в singleTop (тогда этот флаг не нужен):
Но этот вариант мы опустим. Трогать манифест не будем :)
Для обращения к сервису этот флаг нам не потребуется, т.к. сервисы работают совсем не так как обычные Activity.
В конце мы “создаём” PendingIntent обращаясь к getActivity или getService. Оба этих метода принимают 4 значения: context, requestCode, Intent и флаги. В requestCode мы будем заносить ID нашего виджет плюс какую-то цифру, чтобы определить каждый PendingIntent отдельно. Их стандартное поведение может поначалу ввести в заблуждение, но, если коротко, то для разных PendingIntent лучше бы иметь разные коды. Используемый флаг PendingIntent.FLAG_UPDATE_CURRENT говорит системе, что если описываемый PendingIntent уже существует, то следует лишь заменить его extra-данные (те, что мы добавляли используя метод addExtra).
Далее присваеваем каждой кнопке действие и “применяем измененеия”
Перейдём теперь к классу MyIntentService.
Класс наследует IntentService и реагирует ловит входящие Intent в onHandleIntent. При запуске вызыватся конструктор, порождается новая нить, которая после выполнения метода onHandleIntent() завершается.
Переходим к коду. Ничего, чтобы ещё хотелось выделить отдельно у меня нет.
Тут как не было элементов управления так и нет. При получении Intent будет вызываться или onCreate() (если приложение закрыто) или onNewIntent() (если экземляр уже существует). Определением того, что мы получили в прилетевшем Intent будет заниматься метод handleIntent(), который принимает Intent.
В случае, если Intent не пустой и содержит хоть какие-то extras, будем пытаться(!) брать из него предполагаемые данные, т.е. строку “signature” и ID виджета.
Сразу после этого, выведем тост (Toast) с полученой информацией. Если пользователь просто запускает приложение, то ему покажется уведомление с widget ID = 0 и string = null. Далее приложение проверит эти поля и если они отличны от вышеприведённых значений, то присвоит строку в TextView виджета. За присвоение отвечает метод sendBackToWidget().
package com.blogspot.developersu.mywidgetnoconf;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.RemoteViews;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private void handleIntent(Intent i){
String recievedString;
Bundle bndle = i.getExtras();
if (bndle != null) {
int awID = bndle.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
recievedString = bndle.getString("signature");
Toast.makeText(getApplicationContext(), "Got intent from: " + Integer.toString(awID) + " " + recievedString, Toast.LENGTH_SHORT).show();
if (awID != 0 && recievedString != null)
sendBackToWidget(awID, recievedString);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handleIntent(getIntent());
/* Почему мы вызываем handleIntent прямо тут? Потому, что если приложение закрыто а не работает в фоне, то при запуске его из виджета будет вызван
* onCreate. Очевидно, что если это приложение уже будет находиться в стеке, то вызываться будет что-то другое :) */
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
//setIntent(intent); // Удобно использовать, если бы наш Intent был объявлен глобальной переменной. Тогда бы мы просто переопределили его через setintent().
handleIntent(intent);
}
private void sendBackToWidget(int widgetID, String str){
AppWidgetManager awManager = AppWidgetManager.getInstance(getApplicationContext());
RemoteViews rv = new RemoteViews(getPackageName(), R.layout.my_widget_layout);
rv.setTextViewText(R.id.widgetText, str);
awManager.updateAppWidget(widgetID, rv);
}
}
Код не идеален и оставляет множество мест для оптимизаций. Впрочем, нет ничего хуже, чем преждевременная оптимизация, так что оставлю это вам.
Структурированно рассказать о том, как работает виджет сложновато, т. к. все составляющие очень тесно взаимосвязаны и всё это вызывает друг-друга туда-сюда как только хочет. Но давайте попробуем разобраться на живом примере. Полный код того, что получилось в конце страницы и на GitHub.
Виджеты бывают с «окном» конфигуратора и без него. Это «окно» является Activity, вызываемым при первом добавлении виджета на экран. Виджеты с ним и без него несколько отличаются в поведении. Такой конфигуратор нужен в основном для того, чтобы предопределить параметры для каждого нового добавляемого виджета. Это не значит, что добавляемые виджеты не могут быть одинаковыми, т.е. они могут, но без конфигуратора они будут одинаковыми гарантированно.
Итак конфигуратор, это обычная Activity которая получает параметры добавляемого виджета используя Intent, строго говоря она получает EXTRA_APPWIDGET_ID — ID добавляемого виджета и описывает какие параметры будут отображаться на виджете. По завершении, конфигуратор возвращает это значение назад и посылает RESULT_OK используя результирующий Intent. Как сказано ранее, конфигуратор должен не только вернуть эти данные, но и обновить объект RemoteViews, используемый для настройки отображаемых в виджете вещей, т. к. при добавлении виджета с помощью конфигуратора метод onUpdate() вызван не будет. Другими словами конфигуратор должен выставить необходимый текст в TextView виджета, добавить слушателей на кнопки виджета и т.п. Определяется Activity конфигуратора в файле настройки XML виджета (см.далее).
В случае, если конфигуратора нет, при добавлении виджета будет вызван метод onUpdate() в котором как правило и описывает большая часть того, что виджет должен делать. Подробнее о методах виджета поговорим чуть позже.
Задачу поставим так: сделать виджет с конфигуратором, который будет представлять собой текст и кнопку. По нажатии на кнопку будет вызываться конфигуратор. В конфигураторе будет EditText и кнопка для применения изменений. По нажатии на неё текст в виджете будет меняться на установленный в EditText конфигуратора.
Шаг 1. Для начала создадим проект с пустой MainActivity (File → New Project → …). Создадим внутри нашего проекта (пусть это будет в пакете java/widgets/) два java класса: MyWidget — сам будущий виджет MyWidgetConfig — окно конфигурации для виджета
Вернёмся к ним позже. Шаг 2. Создаём XML файл в res/xml/example_info.xml ('это наш AppWidget Provider) Это то, что будет описывать нюансы самого виджета и опишет связку будущего виджета с activity конфигуратора (то окно, которое будет запускаться при добавлении виджета на экран):
Пройдёмся по основным составляющим этого файла. configure определяет activity отвественную за конфигурацию виджета при добавлении и вообще. minHeight и minWidth определяет минимальные размеры для корректного отображения виджета. К сожалению, тут нет механизма в стиле “1 клеточка на 2 клеточки”. На странице документации по andoroid есть неплохое описание какие размеры должны быть (См. ссылки в конце статьи). В этом примере 1 клетка в высоту, 2 в ширину. updatePeriodMillis может быть установлен в 1800000 (30мин). Меньше вроде как не умеет (Вообще умеет, но требует реализации таймера). resizeMode определяет как пользователь может изменять размеры виджета. Можно установить в “none”, тогда размеры изменять будет нельзя. Можно в “horizontal”, можно в “vertical”, можно в оба одновременно “horizontal|vertical”. Названия говорят сами за себя. Шаг 3. Заходим в AndroidManifest.xml
Добавляем наш виджет и его кофигуратор в секцию <application>:
Шаг 4. Добавляем UI. Добавляем в layout/ два новых файла через New->XML->Layout XML
widget_main.xml
widget_main_config.xml
В widget_main_config.xml закидываем элементы EditText, Button со следующими ID:
configWidgetText
configWidgetBtn.
Шаг 5. Вернёмся к MyWidget.java
Виджеты являются в определённом роде BroadcastReciever которые обрабатывают определённые события. Как и BroadcastReciever, обрабатывают действия они методом onRevieve() который в дальнейшем обрабатывает данные другими методами. Тем не менее, виджет является наследником класса AppWidgetProvider (в свою очередь, наследник того самого BroadcastReciever), который всё несколько упрощает.
Рассмотрим что нам предлагает AppWidgetProvider: onEnabled — вызывается один раз, когда виджет создаётся (добавляется на экран). onUpdate — вызывается при обновлении виджета в заданный в XML файле интервал. Основной функционал виджета находится именно здесь. Как уже было сказано, если вам нужно вызывать этот метод чаще чем раз в 30 минут, следует добавить таймер (используя класс AlarmManager). onDeleted — вызывается, когда виджет удалён с экрана. onDisabled — вызывается, когда все виджеты удалены с экрана. Т.е. можно добавлять сколько угодно экземпляров этого виджета, и когда последний из добавленных будет удалён, этот метод сработает. В этом и отличие от onDeleted onRestored — вызывается при восстановлении AppWidget из бекапа. Откликается на ACTION_WIDGET_RESTORED. В обычном случае будет сразу вызван onUpdate. Даже не знаю как этот метод можно использовать. OnAppWidgetOptionsChanged — вызывается при изменении размеров виджета (да, если такие изменения вообще разрешены в XML настройках).
onReceive — вызывается для обработки событий другими методами.
Наследуем наш класс от AppWidgetProvider (extends AppWidgetProvider).
Жмём правой кнопкой на классе, потом Generate -> Override Methods…
Выбираем onUpdate и onDelete. Первый будет вызываться при обновлении виджета, второй — при удалении.
Для меня стало открытием, так что сейчас покажу :)
Для разнообразия, добавим всплывающее окно при удалении виджета через Toast:
Важно!
Этот метод будет вызываться в зависимости от настроек из res/xml/example_info.xml но при создании через конфигуратор он вызван не будет. Будет вызван onEnabled() (но и то как-то, по моим ощущениям, кривовато).
Для начала, установим что-нибудь в первое поле TextView. Какой смысл добавлять что-то в TextView, если это изменит текст через заданный интервал, тем более, что мы этот текст выставляем в конфигураторе? Смысла в этом нет. Просто это даст чуть более полную картину того, как работать с виджетами. В чём есть смысл, так это в определении кнопке действия, т.к. после перезагрузки устройства работать она перестанет (мы говорим о том, что если действие для кнопки будет определяться в конфигруаторе, то эта привязка перестанет работать после перезагурзки, т.к. в этом случае созданием виджета будет заниматься не конфигуратор а система). Но определять их надо в onEnabled, т.к. после перезагруки именно он и будет вызван.
Создадим экземпляр класса RemoteViews (референс: RemoteViews(String packageName, int layoutId) ). Где обращаемся к context из пренимаемых onUpdate параметров (берем оттуда имя) и layout самого виджета. Установим текст "Hello widget!" методом .setTextViewText (референс: setTextViewText(int viewId, CharSequence text)). После чего добавим ещё одну строку, которая укажет приложению на необходимость применить сделанные изменения. Такой финт не типичен для работы с Activity, но для виджетов каждое изменение в виджете должно сопровождаться обновлением.
Без такого обновления будет вылетать ошибка “Problem Loading Widget”. Проделывать эти действия надо со всеми appWidgetIds. Используем конструкцию for (foreach):
for (int appWidgetId : appWidgetIds) {
RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.widget_main);
view.setTextViewText(R.id.widgetText, "Hello widget!");
appWidgetManager.updateAppWidget(appWidgetId, view);
}
Добавим метод onEnabled и назначим
кнопке на виджете действе: вызов конфигуратора по нажатию. В обычной
Activity достаточно было бы создать Intent, но в виджетах используется
несколько более сложная схема.
Итак, необходимо:
Создать Intent. В качетсве параметров — контекст и имя класса Activity-сонфигуратора с .class в конце.
Добавить в него ID виджета, который запрашивает вызов конфигуратора
(с помошью .putExtra). Ожидается переменная int для «поля»
AppWidgetManager.EXTRA_APPWIDGET_ID
Создать объект PendingIntent, который будет назначен кнопке для посылки-по-нажатию. См.аргументы ниже.
Попутно определим все необходимые параметры ещё раз (тут не плохо было бы подумать о том, как не повторяться с тем, что мы описывали в onUpdate). В отличии от onUpdate, принимаемое значение метода только context, так что надо вытащить откуда-то и ID. Для этого вводим объект класса ComponentName. Итак код:
@Override
public void onEnabled(Context context) {
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_main);
AppWidgetManager awm = AppWidgetManager.getInstance(context);
ComponentName compName = new ComponentName(context, MyWidget.class);
int[] widgetIds = awm.getAppWidgetIds(compName);
for (int widgetId : widgetIds) {
Intent intentBtnPwr = new Intent(context, MyWidgetConfig.class);
intentBtnPwr.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
PendingIntent pi = PendingIntent.getActivity(context, widgetId, intentBtnPwr, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.widgetBtn, pi);
awm.updateAppWidget(widgetId, remoteViews);
}
}
Шаг 6. Перейдём к MyWidgetConfig.java
Объявляем переменные для всего класса.
EdText — поле ввода на экране конфигурации виджета
awManager — менеджер, который понадобиться для работы с виджетом,
thisContext — контекст
awID — переменная для хранения ID добавляемого виджета
Кнопку добавлять не будем, просто привяжем её к Listener и опишем необходимое поведение позже.
public class NoWolWidgetConfig extends Activity {
EditText EdText;
AppWidgetManager awManager;
Context thisContext;
int awID;
...
Наследуем класс от Activity, добавляем метод onCreate через Generate… (тем же способом, что описан выше).
Ну и сразу привязываем класс к layout.
Опишем Intent, чтобы получить информацию для текущей Activity о том, какой виджет вызвал эту Activity. В первый раз, при добавлении виджета, он помещается на экран и получает свой ID, после чего запускается конфигуратор. Если результат выполнения конфигуратора не успешен, или он был закрыт (кнопкой назад, например) виджет удаляется, т. к. не получает результрующего статуса RESULT_OK от конфигуратора. Также во входящем Intent конфигуратор получает ID экземпляра виджета и обязан по завершению передать его обратно, также используя механизм Intent.
Объявим Intent и привяжем его к полученному:
Intent intent = getIntent();
Создадим хранилище (Bundle) и выгрузим в него данные из полученного интента —присвоем awID значение ID виджета. В случае неудачи просто закроем конфигуратор и тогда виджет создан не будет.
Теперь определим контекст в переменную и с его использованием привяжем AppWidgetManager к нашему классу (нужен для такой же работы с виджетом, как и внутри самого кода виджета). И да, мы уже делали такое чуть ранее:
Назначим кнопке действие тем же способом, что и ранее мы проделывали в виджете в методе onEnabled.
Intent intentBtnPwr = new Intent(thisContext, MyWidgetConfig.class);
intentBtnPwr.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, awID);
PendingIntent pi = PendingIntent.getActivity(thisContext, awID, intentBtnPwr, PendingIntent.FLAG_UPDATE_CURRENT);
awRV.setOnClickPendingIntent(R.id.widgetBtn, pi);
После всего этого необходимо обновить виджет используя метод .updateAppWidget объекта класса AppWidgetManager. Он обновит в виджете только то, что мы изменили в виджете и не тронет другое. Проще говоря, смотрите на это как на кнопку «Применить».
awManager.updateAppWidget(awID, awRV);
Теперь мы передаём результат (ID виджета) в Intent в наш виджет и закрываем Activity конфигуратора. Так виджет будет знать, что конфигуратор успешно выполнился и будет отображен на экране. Точнее сказать, он не будет с него удалён, что произошло бы при закрытии конфигуратора (повторяюсь).
Intent resultIntent = new Intent();
resultIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, awID);
setResult(RESULT_OK, resultIntent);
finish();
Вот что у нас получилось:
Получилось вертикальное видео. На этом пожалуй всё. Как и обещал, код основных компонентов полностью:
manifests/AndroidManifest.xml
package com.blogspot.developersu.we.widgetexample;
/*
* It doesn't make sense to cover this code by any license, 'cause everything described here
* has been widely used by thousands of coders all around the world. So most likely I've already
* stolen this code in their eyes and you're going to steal it from me. LOL.
* Bye!
*
* Dmitry Isaenko,
* 2017, Russia
* https://developersu.blogspot.com/2017/07/androidwidget.html
*
*/
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
int awID;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
p.s. Есть небольшая проблема с MainActivity. Если он открыт (onPause, а не onActive, очевидно), и вы вызываете конфигуратор кнопкой на виджете, то MainActivity будет отображен после внесения изменений, хотя ожидается увидеть экран с виджетом. Виджет при этом отобразит сделанные в кнофигураторе изменения.