wtorek, 23 września 2014

Pojedynek z Androidem - odc.9 notyfikacje (jeszcze o nich), widgety, sny

Przyszła pora na interakcję użytkownika z aplikacją, kiedy nie jest uruchomiona. Hm… rzecz dość typowa choćby w kafelkowym Windows. Przyznam się. Po krótkiej przerwie miałem ochotę trochę pohejtować na Adroida.  Niemniej jednak po obejrzeniu filmów z dzisiejszej działki dochodzę - także i w tym odcinku - do wniosku, że widzę pewne plusy i minusy. Spróbuję jak poprzednio omawiane pojęcia tłumaczyć równoważnikami z ekosystemu Windows, co pozwoli mi czynić porównania. A więc do rzeczy:

Notyfikacje - ostatnio byłem świadomy jedynie podstaw z podstaw, dziś jest lepiej, aczkolwiek cały czas nie rzucił mi się przed oczy odpowiednik notyfikacji push, ciągle jesteśmy w obszarze notyfikacji lokalnych.  Kolejne wersje Androida coś tam dokładały do tematu. Opcja zapobiegania przed zamknięciem przez użytkownika, IMHO furtka dla natrętów (w Windows póki co to takiej opcji nie ma, choć pewne notyfikacje np. takie ala Skype mogą trwać dłużej niż inne). Z kolei możliwość pozycjonowania przekładające się na miejsce na liście centrum notyfikacji wydaje się niezłym pomysłem. Numer?  Jakieś echo licznika z kafelka? Podobieństwo tylko częściowe, w notyfikacjach Android numer oznacza raczej większą niż 1 liczbę wiadomości, które wszystkie możemy wrzucić do jednej notyfikacji. Wiadomość powitalna niczym górny toast notification - nie jest to już pewien przerost formy nad treścią, by do właściwej notyfikacji z chwilą jej pierwszego pojawienia wyświetlać drugi komunikat?  Więcej informacji po rozwinięciu?  Może OK, ale w sumie niewiele wnosi większy obrazek czy dłuższy tekst… A notyfikacja powinna być krótka, treściwa IMHO.  Dorabianie na sztywno stosu Back przy wejściu przez notyfikację?  Tu się zastanawiam, czy to fajne, że zawsze ze szczegółów Back wrócimy (czy też wejdziemy) na listę… Ale jest to opcja, więc to nasz wybór (w Windows też można modyfikować stos back). Notyfikacje wywoływane przez serwis,  przez taska w tle to często spotykana rzecz w rodzinie Windows. W Androidzie mamy podobną możliwość, jedynie sztywna metoda startforeground przyjmująca notyfikację podoba mi się już mniej, w Windows nie ma żadnych różnic w wywołaniu notyfikacji w aplikacji i tasku. Interaktywność i akcje na notyfikacjach?  No tu czuję, że taka funkcjonalność mogłaby się przydać w Windows… Choć pewnym bardziej odległym skojarzeniem jest w WP 8.1 kontrolka ContentDialog służąca do robienia okien dialogowych z własną dowolną zawartością XAML.  W Android podobać się może kompatybilność wstecz, czyli korzystanie z nowych funkcjonalności na starszych platformach dzięki dołączanej bibliotece.  Można używać nowszych np. parametrów konfigurujących notyfikację, a automatycznie na niektórych starszych  platformach w najgorszym przypadku otrzymamy zachowanie standardowe, jeśli tego nie obsługują.  W Windows target app-ki determinuje zawsze dostępne API,  nie ma oglądanie się za czy przed siebie (nielicznym wyjątkiem była zdaje się kiedyś możliwość korzystania przez refleksję w runtime z dll-ki nowszego WP w starszej app-ce).  Własny layout w notyfikacji?  Hm, mogłoby się to przydać w Windows.  Kojarzy mi się tutaj komunikat o wyjęciu USB z Windows 9, który jakoś wygląda mniej typowo niż reszta, bo ma dwie ikony…  Kto wie, może się kiedyś doczekamy.

Widgety - słowo widget brzmi dla mnie jakoś starodawnie, kojarzy mi się z Windows Vista, którego był jedną ze sztandarowych nowości i przestał być eksponowany w Windows 7. Świat praktycznie o nich zapomniał w Windows.  Niemniej jednak mniej interaktywne i bardziej jednolite kafelki stały się głośne. A MS Research pracuje nad interaktywnymi kafelkami, czyli jakby nie patrząc nad bardziej nowoczesną formą widgetów. Ale wróćmy do Androida. Podobnie jak w Windows z kafelkami pewne rzeczy możemy zdefiniować w manifeście, który dodatkowo wskazuje tutaj na osobny plik XML oraz klasę wywodzącą się z AppWidgetProvider. Obrazek do zasobnika widgetów, początkowy layout trochę jakby przerost formy nad treścią wynikający z OS, w Windows wprost z listy aplikacji możemy przypiąć app-kę w postaci kafla na ekran startowy lub dodatkowe kafle z poziomu samej już aplikacji. W Android widgety nie muszą być tylko na ekranie startowym, mogą być w każdym AppWidgetHost.  W rodzinie Windows “kafelki” możemy sobie sami narysować w kontrolce Hub (pamiętam, że kiedyś w Windows Phone SDK pojawiła się kontrolka imitująca kafelki niemal jak z ekranu startowego). Deklaratywne definiowanie kafelków oraz ich modyfikacja z poziomu kodu jest prostsza w Windows,  ale i same kafelki są prostsze. Mają narzucony duży wybór szablonów, jedynie w WP 8.1 pojawiała się możliwość wrzucania dowolnej zawartości XAML do kafelka (dzięki XamlRenderingBackgroundTask). W kafelkach Windows z reguły mamy standardowe animacje, podmiany zawartości, nie mamy interaktywności z własnymi przyciskami powiązanymi z akcjami.  Aczkolwiek prostota i spójność wizualna systemu na ekranie startowym mogą się podobać, choć są też i pewnym ograniczeniem.  W Windows 9 w menu aplikacji Modern pojawiło się miejsce na własne komendy aplikacji, ale to bardzo odległe skojarzenie.  W Android mamy menadżer wywodzący z AppWidgetProvider zarządzający wszystkimi instancjami widgetu, dostajemy informacje co się z nimi dzieje, w Windows nie ma takiego centralnego sterowania.  Aktualizacja widgetów w Android z poziomu aplikacji jest dość przekombinowana. W Windows jest bardzo intuicyjna i prosta, aczkolwiek nie mówimy tam o podmianie fragmentów dowolnej zawartości, tylko o podmianie kilku pól określonych w API  lub o wyrenderowaniu od nowa całej zawartości.  Możliwości prostego i okresowego odświeżania jest jednak kilka, możemy nawet tylko deklaracją w manifeście wymusić okresowe odświeżanie obrazka. W Android musi być wszystko bardziej skomplikowane, choćby wariant ze sterowaniem alarmem bedący jakimś kolejnym odpowiednikiem okresowego odświeżania (w Windows notyfikacje powiązane z alarmem nie są na kaflach, są to interaktywne specjalizowane odpowiedniki toast czy też raczej message boxów posiadające przyciski z akcjami).  Android ma opcję okresowego odświeżania określoną w manifeście, natomiast odświeżanie sterowane alarmem może być bardzo precyzyjne czasowo (nawet możemy różnicować okresy, czy decydować czy to nam może obudzić urządzenie, btw budzić urządzenie mogą niektóre triggery do tasków w Windows). Powstaje pytanie czy takie precyzyjne aktualizowanie widgetów jest bardzo potrzebne?  Wyskakujące okna alarmu np. z budzącym mnie co dzień dzwonkiem z telefonu bardziej mnie przekonują.  Konfiguracja widgetu?  Dla mnie przerost formy nad treścią, nie licząc już przekombinowanej i złożonej implementacji. Podobać się może podmienialność zawartości, ale upuszczania, otwierania okna z konfiguracją po upuszczeniu, zmiany wyglądu widgetu po zamknięciu go czy nie są zbyt przekombinowane?  W Windows konfiguracja aplikacji jest jedna, na jej podstawie możemy z poziomu kodu aplikacji  wpływać w jakimś stopniu na kafelki, przy czym główny kafelek może jest mniej modyfikowalny, ale wiadomo że reprezentuje wejście na stronę startową aplikacji, a kafelki secondary są odsyłaczami do innych adresów w aplikacji.  Rozumiem, że użytkownik Androida sam upuszcza sobie kilka razy ten sam widget i konfiguruje, co do czego ma mu służyć i jak wyglądać…

Dreams – “sny”, oszczędzacze ekranu,  tak porównując z Windows kojarzy mi się to z aplikacją “Live Lock Screen BETA” w WP 8.1, która pozwala dowolnie modyfikować zawartość lock-screen oraz już bardziej odlegle z modyfikacją tapety lock-screen oraz wyświetlaniem na nim notyfikacji pochodzących od niektórych aplikacji.  Przy czym w Androidzie dreams są wywoływane podczas ładowania lub dokowania urządzenia, a w Windows mamy po prostu modyfikację lock-screen w każdych warunkach.  Nie mamy też przynajmniej na razie otwartego uniwersalnego API do definiowania dowolnej zawartości, ale niedługo to się może zmienić choćby za sprawą osłony API używanego przez  “Live Lock Screen”.

 

Notyfikacje

Ilość informacji w wiadomości oraz poziom interaktywności zależy od wersji systemu

Preferujmy użycie NotificationCompat.Builder

Android Studio

  • Menu kontekstowe na folderze z aplikacją w projekcie -> Open Module Settings –> support-v4-18.0.0

4 podstawowe właściwości wspierane przez wszystkie wersje Android

  • 3 podstawowe związane z wyświetlaniem
    • mała ikona
    • tytuł
    • tekst
  • Akcja obsługująca zdarzenie click w notyfikacji
    • opcjonalne w Android 3.0 (API Level 11) i nowszym
    • wymagana we wszystkich wcześniejszych wersjach

http://developer.android.com/design/style/iconography.html

Android 3.0 (API 11) daje większą kontrolę nad notyfikacjami

  • możliwość wpływania na zachowanie notyfikacji na liście
  • możliwość umieszczania większych ilości informacji na notyfikacji
  • zachowania NotificationCompat.Builder są ignorowane, jeśli nie są wspierane

http://developer.android.com/about/dashboards/index.html

Kontrolowanie sposobu zamknięcia notyfikacji i pozycjonowania

  • opcja automatycznego czyszczenia notyfikacji po zaznaczeniu przez użytkownika
    • Builder.setAutoCancel
    • w większości przypadków powinna być ustawiona na true
  • zapobiegnięcie pozbycia się notyfikacji przez użytkownika
    • Builder.setOngoing
    • ustawiać na true, tylko jeśli jest to absolutnie konieczne
  • kontrola pozycjonowania (które wyżej)
    • Builder.setPriority
    • używajmy z rozwagą ze względu na różnice w specyfice zachowania

Więcej informacji

  • Builder.setLargeIcon - duża ikona różna od tej ikony ze statusu
  • Builder.setNumber - liczba na notyfikacji (zwielokrotnione notyfikacje)
  • Builder.setTicker - specjalna wiadomość wyświetlana podczas pierwszego pojawienia się notyfikacji, u góry ekranu przysłaniając na jakiś czas pasek systemowy

builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),  R.drawable.notification_xxx))

 

ArrayList<String> textValues = new ArrayList<String>();

textValues.add(detailText1);

textValues.add(detailsText2);

 

Intent intent = new Intent(this, XListActivity.class);

intent.setAction(“NotifyMulti”);

intent.putExtra(XListActivity.TITLE_EXTRA, title);

intent.putExtra(XListActivity.TEXT_VALUES_EXTRA, textValues);

builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),  R.drawable.notification_yyy))

.setNumber(count);

.setTicker(“aaa”); 

Rozszerzalne notyfikacje

Android 4.1 (API 16) wprowadził rozszerzalne notyfikacje

  • mają standardowy widok z możliwością rozwinięcia by pokazać więcej informacji
  • tworzone za pomocą klas styli za pomocą klasy NotificationCompat
    • Builder.setStyle
    • na wcześniejszych platformach zostanie użyty standardowy widok
  • BigTextStyle - pole tekstowe, które może przenosić tekst do następnych linii
  • BigPictureStyle - obrazek rozciągający się do wysokości 240dp
  • InboxStyle -  wiele pól tekstowych (jedno w każdej linii)

NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle();

bigTextStyle.setBigContentTitle(bigTitle)

        .setSummaryText(bigSummary)

        .bigText(notificationText);

builder.setStyle(bigTextStyle);

 

NotificationCompat.BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();

bigPictureStyle.setBigContentTitle(bigTitle)

         .setSummaryText(bigSummary)

         .bigPicture(BitmapFactory.decodeResource(getResources(),  R.drawable.xyz));

Interaktywne notyfikacje

Otwarcie aktywności z poziomu notyfikacji często burzy nawigację

  • domyślnie, notyfikacje nie tworzą przycisku back z historią (następuje powrót do ekranu startowego)

Tworzenie spójności w nawigacji

Potrzebujemy dołączać informacje o stosie back do notyfikacji

  • aplikacja z docelową aktywnością potrzebuje przechowywać hierarchię aktywności w manifeście
  • musimy przechowywać hierarchię aktywności w instancji TaskStackBuilder
  • z android.app.TaskStackBuilder tworzymy PendingIntent z odpowiednim stosem back
  • tylko na urządzeniach z Android 4.1 lub nowszym
  • android.support.v4.app.TaskStackBuilder - wsparcie dla starszych urządzeń

Jeśli otworzymy notyfikację prowadzącą do maila, to dzięki deklaracji stosu przyciskiem Back możemy “powrócić” (czytaj wejść) na listę maili

Context context = getActivity();

 

Intent intent = new Intent(“xxx”);

intent.putExtra(abc_index, 0);

 

TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context);

taskStackBuilder.addNextIntentWithParentStack(intent);

PendingIntent pendingIntent = taskStackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);

 

NotificationCompat.Builder builder = new NotificationCompat.Builder(context);

builder.setSmallIcon(R.drawable.efg)

.setAutoCancel(true)

.setContentTitle(“aaa”)

.setContentText(“bbb”)

.setContentIntent(pendingIntent);

Notification notification = builder.build();

 

<activity

          android:name=”.XXX”

          android:label=”Abc”

          android:parentActivityName=”YYY”>

          <meta-data   //dla wcześniejszych od 4.1

                  android:name=”android.support.PARENT_ACTIVITY”

                  android:value=”YYY” />

           <intent-filter>

                   …

           </intent-filter>

</activity>

Android  Studio

  • New  -> Activity –> Blank  Activity –> uzupełniamy Hierarchical Parent

Notyfikacje i serwisy

Notyfikacje mogą dostarczyć UI dla serwisów

  • same jako lekkie UI
  • mogą odpalać aktywności sterujące serwisem
  • serwisy polegające na kontroli notyfikacji często są serwisami foreground

Serwisy foreground

Foreground serwis to taki, który bezpośrednio wpływa na user experience

  • wykonywanie jako foreground service podnosi priorytet serwisu (priorytet czasu życia bardzo zbliżony do aktywności)
  • wyświetla notyfikacje podczas wykonywania (brak specjalnych wymagań dla notyfikacji)
  • serwis staje się foreground serwisem przez wywołanie startForeground (przekazujemy id notyfikacji i instancje Notification)
  • serwis pozostaje w foreground dopóki nie zostanie zamknięty lub nie wywołamy stopForeground

start w serwisie:

Intent activityIntent = new Intent(this, YActivity.class);

PendingIntent activityPendingIntent = PendingIntent.getActivity(this, 0, activityIntent);

 

NotificationCompat.Builder builder = new NotificationCompat.Builder(this);

builder.setSmallIcon(R.drawable.xxx)

       .setContentTitle(“YYY”)

       .setContentText(“…”)

       .setContentIntent(activityPendingIntent);

 

Notification notification = builder.build();

startForeground(SERVICE_NOTIFY_ID, notification);

stop w serwisie

stopForeground(true);

Akcje notyfikacji

Akcje dostarczają przyciski bezpośrednio na notyfikacjach

  • wspierane od Androida 3.0 (API 11)
  • do 3 akcji na notyfikacji  (renderowane jako ikona + tekst)
  • każdy przycisk odpala osobny PendingIntent
  • unikajmy stosowania w aplikacjach z docelowym API niższym niż 11 (na platformach wspierających akcje przyciski są renderowane na biało)

public class XService extends Service  {

        …

        public static Intent getStopIntent(Context context)  {

                 Intent intent = new Intent(context,  XService.class);

                 intent.setAction(“stop”);

                 return intent;

        }

        …

        private void handleStart()  {

                …

                Intent stopServiceIntent = getStopIntent(this);

                PendingIntent  stopServicePendingIntent = PendingIntent.getActivity(this, 0, activityIntent, 0);

 

                NotificationCompat.Builder builder = new NotificationCompat.Builder(this);

                builder.

                        …

                        .addAction(R.drawable.ic_action_cancel,  “Stop”,  stopServicePendingIntent);

                …

        }

}

Niestandardowe layouty

Notyfikacje mogą wyświetlać layout zdefiniowany przez aplikację

  • własne hierarchie widoków
    • notyfikacje wykonują się w procesie zarządzanym przez system
    • hierarchia widoków jest zawarta w procesie naszej aplikacji
    • normalnie nie można przekazywać hierarchii widoków pomiędzy procesami
  • klasa RemoteViews pozwala przekazywać hierarchię widoków między procesami
    • używamy NotificationCompat.Builder.setContent
  • obiekty PendingIntent związane z elementami wizualnymi layoutu

Context context = getActivity();

 

RemoteViews notificationViews =

          new RemoteViews(context.getPackageName(),  R.layout.xxx);

 

Intent stopIntent = XService.getStopIntent(context);

PendingIntent stopPendingIntent = PendingIntent.getService(context, 0, stopIntent, 0);

notificationViews.setOnClickPendingIntent(R.id.btnStop, stopPendingIntent);

 

 

NotificationCompat.Builder builder = new NotificationCompat.Builder(context);

 

builder.

        …

        .setContent(notificationViews);

 

Widgety

Interaktywne komponenty, które żyją na ekranie startowym

  • może być w każdej implementacji AppWidgetHost (najczęściej na ekranie startowym)
  • może dostarczać regularnie odświeżaną zawartość
  • może inicjować akcje w oparciu o interakcję z użytkownikiem

Zachowanie dostarczone przez kombinację systemu i aplikacji

  • początkowy user experience dostarcza system
  • wygląd widgetu i interakacja jest po stronie aplikacji

System

  • AppWidgetManager

Aplikacja

  • Zasoby:  AppWidgetProviderInfo (obrazek preview, layout),  Layout
  • Kod:  AppWidgetProvider

Tworzenie widgetu

  • implementacja widget providera
    • tworzenie jako broadcast receiver
    • zwykle dziedziczymy po AppWidgetProvider
  • informacje o widget providerze w pliku zasobów XML
    • podgląd obrazka
    • layout do początkowego renderowania na ekranie startowym
    • minimalna wysokość i szerokość (w dp) na ekranie startowym
  • powiązanie odpowiednich informacji z widget providerem w manifeście aplikacji
    • filtr intencji dla akcji APPWIDGET_UPDATE
    • wpis w metadanych z informacjami o widget providerze
      • nazwa:  android.appwidget.provider
      • zasób: nazwa zasobu z informacjami o widget providerze

public class XWidget extends AppWidgetProvider  {

       public void onReceive(Context context, Intent intent)  {

               super.onReceive(context, intent);

       }

}

 

<appwidget-provider …

        android:minWidth=”180dp”

        android:minHeight=”110dp”

        android:previewImage=”@drawable/xxx”

        android:initialLayout=”@layout/yyy” >

</appwidget-provider>

 

<manifest>

      <application  …>

                …

                <receiver

                          android:name=”.XWidget”

                          android:label=”Aaa”>

                          <intent-filter>

                                  <action android:name=”android.appwidget.action.APPWIDGET_UPDATE”/>

                          </intent-filter>

                          <meta-data

                                   android:name=”android.appwidget.provider”

                                   android:resource=”@xml/zyz”/>

                </receiver>

        </application>

</manifest>

Z listy widgetów przeciągamy zrobiony przez nas na ekran startowy.

Provider jest notyfikowany, kiedy widget został umieszczony na ekranie startowym

  • otrzymuje APPWIDGET_UPDATE broadcast
    • mapuje na AppWidgetProvider.onUpdate
    • zawiera unikalny identyfikator dla instancji widgetu
  • zwykle zastępuje początkowy layout nowym layoutem
    • pozwala wiązać akcje z layoutem
  • nie może bezpośrednio załadować layoutu ponieważ widget istnieje w innym procesie, procesie ekranu startowego
  • konstruuje nowy layout przy użyciu RemoteViews
    • wiąże akcje używając PendingIntents
  • używa AppWidgetManager’a do zastąpienia początkowego layoutu nowym
    • AppWidgetManager.updateAppWidget

public class XWidget extends XWidgetProvider  {

        public void onReceive(Context context,  Intent intent)  {

                super.onReceive(context,  intent);

        }

        @Override

        public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)  {

                …

                for (int index = 0; index < appWidgetIds.length; index++)  {

                        …

                        RemoteViews appWidgetViews = getWidgetRemoteViews(context);

                        appWidgetManager.updateAppWidget(appWidgetIds[index],  appWidgetViews);

                }

        }

        public static RemoteViews getWidgetRemoteViews(Context context)  {

                Intent button1Intent = new Intent(context, YActivity.class);

                button1Intent.putExtra(“image_resource_id”, R.drawable.zzz);

                PendingIntent button1PendingIntent = PendingIntent.getActivity(context,  0, button1Intent, PendingIntent.FLAG_UPDATE_CURRENT);

 

                RemoteViews appWidgetViews = new RemoteViews(context.getPackageName(), R.layout.xxx);

                appWidgetsViews.setOnClickPendingIntent(R.id.btn1, button1PendingIntent);

 

                return appWidgetViews;

        }

}

Aktywność wyświetlająca podany w parametrze obrazek

public class YActivity {

        @Override

         protected void onCreate(Bundle savedInstanceState) {

                 …

                 if (savedInstanceState == null) {

                         Intent startIntent = getIntent();

                         int imageViewResourceId = startIntent.getIntExtra(“image_resource_id”, R.drawable.default_image);

 

                         PlaceholderFragment fragment = new PlaceholderFragment();

                         Bundle args = new Bundle(1);

                         args.putInt(“image_resource_id”,  imageViewResourceId);

                         fragment.setArguments(args);

                         getFragmentManager().beginTransaction()

                                .add(R.id.container, fragment)

                                .commit();

                 }

         }

         …

}

Akcje broadcast głównego widgetu

Providery widgetów mogą otrzymywać wiele akcji broadcast

  • jawny filtr intencji APPWIDGET_UPDATE (wszystkie inne akcje są automatycznie routowane do providera)
  • APPWIDGET_ENABLED / AppWidgetProvider.onEnabled - pierwsza instancja widgetu została dodana do ekranu startowego
  • APPWIDGET_UPDATE / AppWidgetProvider.onUpdate - widget wymaga odświeżenia, zawiera informację, kiedy instancja widgetu została dodana do ekranu startowego
  • APPWIDGET_DELETED / AppWidgetProvider.onDeleted  - instancja widgetu została usunięta z ekranu startowego
  • APPWIDGET_DISABLED / AppWidgetProvider.onDisabled - ostatnia instancja widgetu została usunięta z ekranu startowego

Aktualizacja i konfiguracja widgetów

Aktualizacja widgetów

Okresowa aktualizacja zawartości widgetu

  • 3 podejścia (można używać kombinacji)
  • Okres aktualizacji widget providera
  • Aktualizacja wyzwalana przez aplikację
  • Okres aktualizacji wyzwalanej przez AlarmManager

Widget Provider Update Period

  • najprostszy do zaimplementowania
  • atrybut updatePeriodMillis w provider info  (w ms)
  • provider otrzymuje APPWIDGET_UPDATE broadcast Intent
  • budzi urządzenie, jeśli jest uśpione (potencjalnie może wyczerpywać baterie)
  • rekomendowany okresem jest 1 godzina lub więcej
  • wszystkie okresy poniżej 30 minut są traktowane jak 30 minut

<appwidget-provider …

       android:updatePeriodMillis=”3600000”>

Aktualizacja wyzwalana przez aplikację

Aplikacja może aktualizować widget w dowolnym czasie

  • do otrzymania instancji menadżera widgetów używamy AppWidgetManager.getInstance
  • podmieniamy RemoteViews tak jak robimy to w metodzie onUpdate providera - używamy AppWidgetManager.updateAppWidget
  • modyfikacja zawartości RemoteViews stanowi wyzwanie
    • hierarchia kontrolek nie jest jeszcze pobrana i dlatego nie jest dostępna programistycznie
    • musimy zapamiętać słownik elementów wizualnych, które chcemy zmieniać

public class MainActivity extends Activity {

        Context context = getActivity();

 

        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

        ComponentName appWidgetComponentName = new ComponentName(context, XWidget.class);

        int[] appWidgetIds = appWidgetManager.getAppWidgetIds(appWidgetComponentName);

 

        for(int index = 0; index < appWidgetIds.length;  index++)  {

                int appWidgetId = appWidgetIds[index];

 

                RemoteViews appWidgetViews = XWidget.getWidgetRemoteViews(context);

                appWidgetViews.setCharSequence(R.id.txtTitleText, “setText”, “YYY”);

                appWidgetManager.updateAppWidget(appWidgetId,  appWidgetViews);                                 

        }       

}

Aktualizacja wyzwalana przez alarm

AlarmManager dostarcza szczegółową kontrolę nad czasem

  • większa kontrola nad czasem niż provider update period
    • możliwość ustawiania krótszych przedziałów
    • możliwość różnicowania przedziałów czasowych
    • możliwość kontroli, czy urządzenie się obudzi, kiedy jest uśpione
  • AlarmManager wyśle broadcast Intent, jeśli okres się zakończy
    • ogólnie dobrze jest definiować własny Intent
    • można łatwo dodać Intent do tych przechwyconych przez widget provider’a

public class XWidget extends AppWidgetProvider  {

        …

        static PendingIntent getExplicitUpdatePendingIntent(Context context)  {

               Intent intent = getExplicitUpdateIntent(context);

               PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent,  0);

               return pendingIntent;

        }

        static Intent getExplicitUpdateIntent(Context context)  {

               Intent intent = new Intent(context,  XWidget.class);

               intent.setAction(“xxx”);

               return intent;

        }

        public void onReceive(Context context, Intent intent)  {

              String action = intent.getAction();

              if  (“xxx”.equalsIgnoreCase(action))  {

                     doExplicitUpdate(context,  intent);

              }  else  {

                      super.onReceive(context,  intent);

              }            

        }

        private void doExplicitUpdate(Context context,  Intent intent)  {

               AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

               ComponentName appWidgetComponentName = new ComponentName(context, XWidget.class);

               int[] appWidgetIds = appWidgetManager.getAppWidgetIds(appWidgetComponentName);

             

               if (appWidgetIds != null && appWidgetIds.lendth > 0)

                      onUpdate(context,  appWidgetManager,  appWidgetIds);

        }

}

 

public class MainActivity extends Activity  {

        …

        private void btnStartAlarmManagerOnClick(Button v)  {

                Context context = getActivity();

                AlarmManager am = (AlarmManager)  context.getSystemService(Context.ALARM_SERVICE);

                PendingIntent pendingIntent = XWidget.getExplicitUpdatePendingIntent(context);

 

                long currentTimeMillis = System.currentTimeMillis();

                long intervalMillis = 5000;

                //setInexactRepeating w odróżnieniu do setRepeating dostosowuje się do innych czynności w systemie i przyczynia się do większej oszczędności baterii

                am.setInexactRepeating(AlarmManager.RTC, currentTimeMillis + intervalMillis, intervalMillis, pendingIntent);

        }

        private void btnStopAlarmManagerOnClick(Button v)  {

                Context context = getActivity();

                AlarmManager am = (AlarmManager)  context.getSystemService(Context.ALARM_SERVICE);

                PendingIntent pendingIntent = XWidget.getExplicitUpdatePendingIntent(context);

                am.cancel(pendingIntent);

        }

        private static PendingIntent getAlarmPendingIntent(Context context)  {

                PendingIntent pendingIntent = null;

 

                return pendingIntent;

        }

}

 

<application …>

        <receiver  …>

                 <intent-filter>

                         <action android:name=”android.appwidget.action.APPWIDGET_UPDATE”/>

                         <action android:name=”xxx”/>

                 </intent-filter>

                 …

        </receiver>

</application>

 

Konfiguracja aktywności

Widgety mogą mieć związaną ze sobą aktywność konfiguracyjną

  • używana do ustawienia zachowania danej instancji widgetu
  • specyfikujemy nazwę aktywności w atrybucie configure w widget provider info
  • automatycznie wyświetlana po upuszczeniu widgetu na ekran startowy (aktywność musi zawierać filtr intencji dla akcji APPWIDGET_CONFIGURE)
  1. Widget na ekranie startowym
  2. AppWidgetManager –> startActivityForResult (Intent z appWidgetId)
  3. Wybór opcji przez użytkownika
  4. Wykonanie zmian na widgecie (bezpośrednio lub poprzez broadcast intent)
  5. setResult do AppWidgetManager (Intent z appWidgetId)

<appwidget-provider …

        android:configure=”com.abc.xyz.XWidgetConfigActivity”

</appwidget-provider>

 

<manifest>

        <activity 

               android:name=”com.abc.xyz.XWidgetConfigActivity”

               … >

               <intent-filter>

                       <action android:name=”android.appwidget.action.APPWIDGET_CONFIGURE”/>

              </intent-filter>

        </activity>

</manifest>

 

public class XWidgetConfigActivity extends Activity  {

        …

        @Override

        protected void onCreate(Bundle savedInstanceState)  {

               …

               //widget nie zostanie umieszczony na ekranie startowym, jeśli użytkownik zignoruje ekran konfiguracyjny

               setResult(RESULT_CANCELED);

               …

        }

        private void btnDoneOnClick(Button v)  {

               Activity activity = getActivity();

               …

               int appWidgetId = resolveAppWidgetId(activity);

               AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(activity);

               Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);

               options.putBoolean(“option1”, flag);

               appWidgetManager.updateAppWidgetOptions(appWidgetId,  options);

 

               Intent intent = XWidget.getExplicitUpdateIntent(activity);

               intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);

               activity.sendBroadcast(intent);

 

               Intent resultValue = new Intent();

               resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,  appWidgetId);

               activity.setResult(RESULT_OK,  resultValue);

               activity.finish();

        }

        int resolveAppWidgetId(Activity activity)  {

                int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;

                Intent createIntent = getActivity().getIntent();

                Bundle extras = createIntent.getExtras();

                if (extras != null)  {

                        appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,

                                     AppWidgetManager.INVALID_APPWIDGET_ID);

                }

                return appWidgetId;

        }

}

 

public class XWidget  {

         …

         public void onReceive(Context context, Intent intent)  {

              

         }

         private void doExplicitUpdate(Context context,  Intent intent)  {

               …

               int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);

               int[] appWidgetIds = appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID  ? 

                         appWidgetManager.getAppWidgetIds(appWidgetComponentName) : new int[] { appWidgetId };

               …

         }

         …

         public static RemoteViews getWidgetRemoteViews(Context context, int appWidgetId)  {

                …

                AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

                Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);

                boolean use2Buttons = options.getBoolean(“use2Buttons”, true);

                int resourceId = use2Buttons ? R.layout.xxx : R.layout.yyy;

                RemoteViews appWidgetViews = new RemoteViews(context.getPackageName(), resourceId);

                …

         }

}

 

Dreams

UI experience, kiedy urządzenie nie jest aktywnie używane

  • = oszczędzacze ekranów
  • zorientowane na oglądanie, możliwa interaktywnosć, ale zwykle jest bardzo ograniczona
  • wyzwalane kiedy urządzenie jest ładowane lub dokowane
    • może wpływać na stopień naładowania
    • zarządzamy przez Settings –> Display –> Daydream (on/off,  włączanie/wyłączanie aplikacji, na dole - when to daydream: while docked, while charging, either)
  • używają te same layouty i widoki jak aktywności
    • praktycznie nie mają więcej nic wspólnego z aktywnościami
    • wykonywane są przez specjalny typ serwisu
  • implementacje dziedziczą po klasie DreamService
  • zostały dodane w Android 4.2 (API Level 17)

Cykl życiowy

  • onAttachedToWindow - powiązanie z oknem, uruchamianie i ładowanie UI, przypięcie handlerów do zdarzeń
  • onDreamingStarted - wejście w stan wykonywania, inicjalizacja animacji lub innych form pokazujących wykonywanie
  • onDreamingStopped - zakończenie stanu wykonywania, zatrzymanie wszystkiego, co zostało uruchomione w onDreamingStarted
  • onDetachedFromWindow - okno jest czyszczone, czyścimy każdy event handler zapięty w onAttachedToWindow

Tworzenie

  • napisanie serwisu dziedziczącego po DreamService
  • nadpisanie onAttachedToWindow - metodą setContentView ładujemy zasób layoutu
  • dodajemy filtr intencji by uczynić dream widzialnym dla systemu

public class XDreamService extends DreamService  {

       @Override

        public void onAttachedToWindow()  {

                super.onAttachedToWindow();

                setContentView(R.layout.xxx);

        }

}

<service

          android:name=”.XDreamService”

          android:label=”Xxx”>

          <intent-filter>

                    <action android:name=”android.service.dreams.DreamService”/>

                    <category android:name=”android.intent.category.DEFAULT”/>

          </intent-filter>

</service>

Brak komentarzy: