Version | Anpassungen |
---|---|
1.0 (2021-05-05) | Initiale Version |
1.1 (2021-07-11) |
|
1.2 (2021-09-12) |
Wird die Extension in mehreren Apps verwandt, löst ein Antippen einer Aktionsschaltfläche das zugehörige Ereignis in allen Apps gleichzeitig aus. Das liegt an der Globalität der verwendeten Broadcast-Receiver. Abhilfe: Die Intents für die Aktionsschaltflächen sind mit dem App-Namen individualisiert. Beim Empfang einer Broadcast wird der Name überprüft und nur die passenden Nachrichten weiter verarbeitet. Kodular stellt nun auch viele Androidx-Funktionen bereit. Es muss nur noch "androidx.media.jar" eingebunden werden. Der Foreground-Service wird nun per UsesServices-Annotaion in der Quelle und manuell in die Rubrik broadcastReceivers in der generierten .aix-Datei eingetragen. Durch diese Änderungen ist nicht mehr notwendig zwei verschiedene Version der Extension für App Inventor und Kodular zu haben. Anpassung bestehender Kodular-Projekte: Es besteht interne Namensgleichheit. Deshalb kann die alte Version durch die neue ersetzt werden. Dies geht jedoch nur über einen Export des Projekts und einem Reimport.
|
1.3 (2021-09-15) | Wie oben. Nun auch für das Anklicken der Notification und das Löschen individualisierte Intents um Überschneidungen bei gleichzeitiger Benutzung der Extension in mehreren Apps zu verhindern. |
1.4 (2022-10-10) | Angepasst für SDK31 (Android 12): Alle PendingIntent erhalten das Flag FLAG_IMMUTABLE. Der Service erhält das Attribut exported = "true". Globale Exception wird gefangen und ins Log ausgegeben. |
2023-04-19 | Update der im Beispiel verwendeten Extension TaifunPlayer. Die alte Version funktioniert unter Android 11+ nicht mehr. |
2023-08-15 | Seit Android 12 (auch schon früher?) wird die App nicht mehr automatisch geöffnet, wenn man die Notification anklickt. Fabio hat mir gezeigt, was geändert werden musste. |
1.6 (2023-11-23) | Anpassung an Android 14 |
Foreground-Service / Doze-Mode
Schaltflächen in der Media-Notification
Meta-Daten und Erscheinungsbild
Fortschrittsbalken (ProgressBar, ab Android 10, API Level 29)
Zum Verständnis der Implementierung
Bereitstellen der Bibliotheken
Pachten der .aix-Dateien für Kodular
Das ZIP-Archiv UrsAI2MediaNotification zum Download. Das Archiv enthält den Quellcode, das kompilierte Binary zum Upload in den App Inventor und eine Beispiel-Anwendung.
Seit dem API-Level 21 (LolliPop 5.0) gibt es die Media Controls, eine besondere Form der Benachrichtigung (Notification), die eigens zur Steuerung von Mediaplayern entwickelt wurde. Der Clou ist, dass auch Steuerimpulse externer Wiedergabegeräte ausgewertet werden.
Diese Extension ist ausschließlich zu Verwaltung von Media-Notifications gedacht. Wer reguläre Benachrichtigungen erstellen will, sollte die Extension UrsAI2Notifier benutzen.
Mit dem API-Level 26 (Version Oreo 8.0) hat Android das Konzept der Benachrichtigungskanäle eingeführt (NotificationChannel, bei manchen Geräten auch Benachrichtigungskategorien genannt). Sämtliche Benachrichtigungen müssen ab API Level 26 einem solchen Kanal zugeordnet werden. Bis einschließlich API Level 25 sind alle den Benachrichtigungskanal betreffende Angaben und Funktionen wirkungslos.
Das Benachrichtigungskonzept ist relativ komplex. Android erlaubt es, Einstellungen sowohl auf der Geräteebene, als auch auf der Kanalebene und bei den Benachrichtigungen selbst vorzunehmen.
Viele Eigenschaften der Benachrichtigungen, z.B. die Wichtigkeit (Importance), werden auf der Kanal-Ebene festgelegt. Diese Eigenschaften werden von der App bei der Anlage des Kanals festgelegt. Der Benutzer hat mit Hilfe die App Eigenschaften vollen Zugriff auf die meisten der Kanalattribute und kann sie nach belieben modifizieren. Die Modifikationen des Benutzers sollen nicht überschrieben werden. Deshalb können die Kanaleigenschaften nachträglich nicht mehr per Programm geändert werden. Das geht sogar soweit, dass, wenn eine Kanal gelöscht und anschließend wieder angelegt wird, er mit den zuletzt vorhandenen Einstellungen erzeugt wird. Das "Löschen" ist praktisch nur ein "Verstecken". Lediglich die Eigenschaften Name und Description eines Kanals lassen sich nachträglich ändern. Die Beseitigung von Schreibfehler wäre ansonsten unmöglich.
Man sollte sich also sehr gut überlegen, welche Eigenschaften einem Kanal zugewiesen werden.
Die Extension bietet die Eigenschaften ChannelID, ChannelName, ChannelDescription und ChannelImportance zur Festlegung der Kanaleigenschaften. Der Kanal wird direkt nach Start der App angelegt (im Companion erst dann, wenn das erste Mal ShowNotification aufgerufen wird).
Will man andere Eigenschaften ändern, muss ein neuer Kanal (neue ChannelID) verwendet werden. Den bestehenden Kanal versteckt man sinnvollerweise beim Start der App mit HideChannel.
Sämtliche Änderungen haben keine sofortige Wirkung. Erst beim nächsten Aufruf von ShowNotification werden sie übernommen.
FFür manche Projekte ist es notwendig zu verhindern, dass die zugehörige App vom Betriebssystem deaktiviert wird. Mit der Version 6.0 (Marshmallow) hat Android den Doze-Modus zur Optimierung der Akku-Laufzeiten eingeführt. Diese Funktion schaltet, wenn keine App aktiv bedient wird, nach und nach alles herunter (Display, CPU, WiFi, etc.). Um den Doze Mode wirkungsvoll zu umgehen, ist es notwendig, einen Foreground-Service anzulegen. Viele nützliche Hintergrundinformationen findet man im ZEBRA Developer Portal: Keeping your Android application running when the device wants to sleep.
Die meisten Android-Installationen erkennen, wenn ein Mediaplayer aktiv ist und schalten das Gerät nicht ab. Für den Fall, dass dies doch geschieht, kann über die Eigenschaft ForegroundService festgelegt werden, dass bei der Anzeige der Notification ein Foreground-Service gestartet wird. Hat die installierte Android-Version einen API Level von unter 26, ist die Angabe wirkungslos.
Die Schaltfläche mit dem Play(⯈)- bzw. dem Pause(⏸)-Symbol wird immer angezeigt. Welches der beiden angezeigt wird, kann über die Funktionen SetStatePaused (zeigt ⯈ an) und SetStatePlaying (zeigt ⏸) an) eingestellt werden.
Die anderen Schaltflächen lassen sich über die Support...-Funktionen an- und abstellen, je nachdem welche Funktionen die App unterstützt.
Ist ClickAble gesetzt, wird beim Anklicken des Benachrichtigungskörpers die App in den Vordergrund gebracht und das Ereignis OnClick ausgelöst. Ist ClickAble auf false gesetzt, passiert beim Anklicken der Benachrichtigung nichts.
Um die App in dem Ereignis in den Vordergrund zu bringen, ist ein kleiner Trick notwendig. Im Beispiel wird im OnClick-Ereignis ein Screen mit der Bezeichnung Dummy geöffnet. Dieser Screen schließt sich unmittelbar im Ereignis Screen.Initialize selbst wieder:
Wird DeleteAble gesetzt, kann der Anwender die Benachrichtigung, z.B. durch Wischen, löschen. Wenn die Notification vom Anwender gelöscht wird, wird das Ereignis UserCanceled ausgelöst.
Die Hintergrundfarbe ist nicht einstellbar, sondern wird von Android automatisch aus dem Album-Image ermittelt.
Über die Funktion SetMetaData und SetMetaDataEx können Titel, Interpret und und ein Bild (AlbumImage) angegeben werden, die in der Benachrichtigung angezeigt werden.
Für AlbumImage sind folgende Angaben möglich:
Typ | Präfix | Beispiel |
---|---|---|
URL | http oder https | https://ullisroboterseite.de/android-AI2-MediaNotification/MediaNotification.png |
Asset | // oder nichts | //MediaNotification.png oder einfach nur MediaNotification.png |
Datei, relativer Pfad | / | /data/user/0/appinventor.ai_bienonline.UrsMediaNotificationAI2/MediaNotification.png |
Datei, absoluter Pfad | file:/// | file:///Android/data/appinventor.ai_bienonline.UrsMediaNotificationAI2/MediaNotification.png |
Bei der Angabe einer URL ist zu beachten, dass das Bild synchron geladen wird. Dies kann bei großen Dateien oder langsamen Verbindungen einige Zeit in Anspruch nehmen. Eine andere Möglichkeit ist es, eine Extension zu verwenden, die einen asynchronen (nebenläufigen) Download der Datei ermöglicht (z.B. die Extension ImageLoader).
Um den Zugriff auf die Dateien zu erleichtern, gibt es die Funktionen
Die Möglichkeit, eigene Grafiken für das SmallIcon einzubinden (Eigenschaft SmallIconImage), besteht erst ab API Level 23. Deshalb gibt es zusätzlich die Möglichkeit über die Eigenschaft SmallSystemIcon ein System-Icon auszuwählen (für mögliche Angaben siehe: System Notification Icons). Die Auswahlregel ist wie folgt:
Bedingung | Auswahl | ||
---|---|---|---|
API Level | SmallIconImage | SmallSystemIcon | |
≥ 23 | gefüllt | X | SmallIconImage |
leer | gefüllt | SmallSystemIcon | |
leer | leer | kein Icon | |
< 23 | X | gefüllt | SmallSystemIcon |
X | leer | kein Icon |
Ab Android 10 kann die Media-Notification einen Fortschrittsbalken (ProgressBar) und eine Suchleiste (SeekBar) anzeigen. Dazu muss die Spieldauer (Duration) des Media-Objekts und die aktuelle Abspielposition (CurrentPosition) bekannt sein.
SetMetaDataEx erlaubt die Angabe der Spieldauer, die für die Anzeige eines Fortschrittsbalkens (ProgressBar) benötigt wird. Einige Media-Player, wie der im Beispiel benutzte TaifunPlayer, liefern diese Information. Dazu muss der Player aber entsprechend initialisiert sein.
Der Funktion ShowWithProgressBar zeigt einen Fortschrittsbalken an. Er startet an mit der Position, die im Parameter CurrentPosition übergeben wird. Danach läuft er selbständig weiter. Wenn man die Abspielposition per Programm ändert, muss man den Fortschrittsbalken wieder mit ShowWithProgressBar synchronisieren.
Bei Versionen älter als Android 10, funktionieren SetMetaDataEx und ShowWithProgressBar ebenfalls, allerdings wird kein Fortschrittsbalken angezeigt.
Man stellt die initialen Eigenschaften der Komponente passend ein, übergibt die Media-Metadaten (SetMetaData oder SetMetaDataEx) und ruft anschließend ShowNotification bzw. ShowWithProgressBar auf. Von nun an braucht man eigentlich nur noch auf die Ereignisse reagieren. Je nach Ereignis stellt man den Mediaplayer passend ein, passt Eigenschaften der Komponente an und ruft wieder ShowNotification auf.
In der Regel soll der Mediaplayer nicht nur von der Media-Notification gesteuert werden. Meist enthält die App ebenfalls Steuerelemente, die den Player kontrollieren sollen. Um die Blöcke im Projekt einfach zu gestalten, gibt es die Funktionen Play, Pause, Rewind, etc., die die gleichen Ereignisse auslösen, wie die Notification. Es ist also keine doppelte Programmierung für die Steuerelemente auf der Benutzeroberfläche notwendig. Es muss lediglich die passende Funktion aufgerufen werden.
Fehler werden über das Ereignisse Screen.ErrorOccurred gemeldet.
Code | Text | Bedeutung | Anmerkung |
---|---|---|---|
17500 | Album image not found. | Das angegebene Album-Image konnte nicht geladen werden. | Betroffene Funktionen sind SetMetaData und SetMetaDataEx. |
17501 | No meta data set. | Die Benachrichtigung soll angezeigt werden, ohne dass vorher SetMetaData oder SetMetaDataEx aufgerufen wurde. | Betroffene Funktion ist ShowNotification. |
17502 | SmallIconImage invalid. | Die Grafik-Datei beim Parameter SmallIconImage kann nicht geladen werden. | Betroffene Funktion ist ShowNotification. Die Benachrichtigung wird ohne ein Icon angezeigt. |
17503 | SmallSystemIcon invalid. | Die Angabe beim Parameter SmallSystemIcon kann nicht aufgelöst werden. | Betroffene Funktion ist ShowNotification. Die Benachrichtigung wird ohne ein Icon angezeigt. |
17504 | Cannot start foreGround service. | Der angeforderte Foreground-Service kann nicht gestartet werden. | Betroffene Funktion ist ShowNotification. |
17506 | Not an UrsMediaHelper component. | Die übergebene Komponente ist nicht vom Typ UrsMediaHelper. | Betroffene Funktion ist SetMetaDataFromMH. |
GetAppDataDir()
auf meinem Testgerät für das Beispiel-Programm liefert folgendes Ergebnis:GetDownloadDir("file://")
auf meinem Testgerät für das Beispiel-Programm liefert folgendes Ergebnis:getVolumes("file://)
gibt auf meinem Testgerät folgende Elemente:Das Download-Archiv enthält ein Beispiel-Projekt in zwei Versionen, je eine für App Inventor und Kodular:
UrsMediaNotification
Das Download-ZIP-Archiv enthält im Verzeichnis example je eine Beispielprojektdatei für App Inventor (UrsMediaNotification.aia) und für Kodular (UrsMediaNotificationK.aia).
Das Beispiel demonstriert, wie ein Mediaplayer mit der Extension gesteuert werden kann. Als Player wird der TaifunPlayer verwendet. Es können vier verschiedene Musikstücke abgespielt werden.
In Kodular können die Fonts der Schaltflächen individuell eingestellt werden. U.a. steht dort der Material Icons Font mit vielen nützlichen Symbolen von Google zur Verfügung. Für den App Inventor wird die Extension MyFonts von Anke benutzt.
Die Steuerelemente werden je nach aktuellen Zustand der App freigegeben oder gesperrt. Sie bewirken
Die zugehörige MediaNotification dupliziert diese Steuerelemente. Es werden nur die freigegeben Elemente angezeigt (Off Broadway ist das letzte Stück in der Playlist, SkipToNext ist deshalb gesperrt und wird nicht angezeigt):
(Screenshot Xiaomi Redmi 5 Plus, Android Oreo 8.1)
Die Anzeige eines Stoppsymbols steht in der Android Media-Notification leider nicht zur Verfügung.
In Android 10 wird die Hintergrundfarbe automatisch an die Farbe des Album-Bildes angepasst.
(Screenshot Google Pixel 2 XL API 30 Emulator)
Ein Fortschrittsbalken (ab Android 10) wird eingeblendet, wenn man die Notification aufzieht:
Die Prozedur SetMusic füllt die Extension mit den Metadaten (Titel, Interpret, Album-Bild). Die MP3-Dateien in dem Beispielprojekt enthalte bereits alle benötigten Metadaten, so dass diese einfach mit Hilfe der UrsMediaHelper-Extension übergeben werden können. Die Metadaten lassen sich z.B. mit dem Programm Mp3tag einsehen und bearbeiten.
Bei der Initialisierung wird dafür gesorgt, dass die App gleich zu Beginn korrekt arbeitet. SetMusic stellt die Metadaten und die Musikquelle für den TainfunPlayer auf das erste Musikstück ein. Mit der Kombination MediaPlayer.Start und MediaPlayer.Pause wird erreicht, dass die Eigenschaften Duration und CurrentPosition des Mediaplayers abgerufen werden können, ohne dass er anfängt zu spielen. MediaNotification.SetStatePaused und MediaNotification.ShowNotification zeigen die Notification im Zustand Pause (Symbol ⯈) an. WakeLock.RequestBatteryOptimization setzt die App auf die WhiteList. Beim ersten Start der App erhält der Anwender eine Anfrage, die Erlaubnis hierzu anfordert.
Bei der Änderung der Option WithForegroundService muss ein ggf. bereits gestarteter Foreground-Service mit der Anweisung MediaNotifiaction.Stop gestoppt werden. Diese Anweisung löst das Ereignis OnStop aus. Im Normalfall wird in diesem Ereignis der MediaPlayer gestoppt. Damit dies jedoch nicht beim Umschalten der Option geschieht, wird dies mit Hilfe der Variablen DontStopPlayer verhindert.
Beim Weiterschalten zum nächsten Musikstück (SkipToNext) und beim Zurückschalten (SkipToPrevious) wird zunächst der der aktuelle Zustand des Mediaplayers in einer lokalen Variablen zwischengespeichert. Dieser soll nach dem Umschalten wieder hergestellt werden. Danach wird der Player gestoppt. Das nächste (das vorhergehende) Musikstück wird ermittelt und dabei dem Zustand der Playlist entsprechenden Schaltflächen freigegeben bzw. gesperrt. Die Blöcke werden über die Prozedur SetMusic mit den Daten des neu ausgewählten Musikstücks gefüttert. Zum Schluss werden entsprechend des gespeicherten Player-Zustands die Ereignisse OnPlay bzw. OnPause ausgelöst. In den Ereignissen wird dafür gesorgt, dass der Mediaplayer und die MediaNotification passen eingestellt werden.
Die Extension kann zusammen mit der UrsAI2WakeLock-Entension verhindern, dass der Doze-Modus zuschlägt und das Smartphone abschaltet. Dazu wird ein ForegroundService gestartet. Dieser Service muss in der Manifest-Datei der App deklariert werden. Im App Inventor geht dies, Kodular hat noch nicht nachgezogen. Hier muss die erstellte .aix-Datei nachträglich gepacht werden.
Die folgende Beschreibung setzt voraus, dass die Entwicklungsumgebung nach dem in AI2 FAQ: Extensions entwickeln beschriebenen Schema eingerichtet wurde.
Bei Verzeichnisangaben in den folgenden Abschnitten muss <user> stets durch den entsprechenden Namen ersetzt werden!
Die Extension verwendet viele Android-Klassen aus der Androidx-Bibliothek. Das ZIP-Archiv enthält im Verzeichnis de.ullisroboterseite neben den Verzeichnissen für den Source-Code der Extension auch die Androidx-Bibliotheksdateien: androidx.core-1.3.2-runtime.jar. Diese Bibliothek muss zusätzlich in die Extension eingebunden werden.
Damit die Bibliotheken in die Extension eingebunden werden, müssen sie in das vorgesehene Verzeichnis kopiert und im Source-Code deklariert werden.
Das Kopieren geschieht über das Build-Script. Dazu muss in der Datei
C:/Users/<user>/appinventor-sources/appinventor/components/build.xml
folgender Eintrag in der Rubrik CopyComponentLibraries gemacht werden:
<copy toFile="${public.deps.dir}/androidx.media.jar" file="C:/Users/<user>/appinventor-sources/appinventor/components/src/de/ullisroboterseite/androidx.media-1.3.1-runtime.jar"/>
In der Quelle wird die benutzte Bibliothek über die Annotation @UsesLibraries deklariert und der Foreground-Service über Annotation @UsesServices:
@UsesServices(services = {
@ServiceElement(name = "de.ullisroboterseite.ursai2medianotification.ForegroundService", enabled = "true", exported = "true") })
@UsesLibraries(libraries = "androidx.media.jar")
Die Umgebung ist nun so weit vorbereitet, dass die Extension kompiliert werden können.
Der Build-Vorgang erstellt die Datei de.ullisroboterseite.ursai2medianotification.aix. Diese Datei hat das Format einer ZIP-Datei, kann also mit einem entsprechenden Programm geöffnet und bearbeitet werden. Gegebenenfalls hilft es die Datei in ...zip umzubenennen.
Die .aix-Datei enthält den Ordner de.ullisroboterseite.ursai2medianotification und darunter den Ordner files. Im Ordner files befinden sich die beiden Dateien component_build_info.json und component_build_infos.json. Beide können mit einem einfachen Texteditor geöffnet und bearbeitet werden. In beiden Dateien befindet sich der Eintrag
"services":["<service android:name=\"de.ullisroboterseite.ursai2medianotification.ForegroundService\" android:enabled=\"true\" android:exported=\"false\">\n <\/service>\n"]
und der Eintrag
"broadcastReceivers":[] (nicht verwechseln mit "broadcastReceiver":[]) !
Der Text zwischen den eckigen Klammern bei services muss in die eckigen Klammern bei broadcastReceivers kopiert werden:
"broadcastReceivers":["<service android:name=\"de.ullisroboterseite.ursai2medianotification.ForegroundService\" android:enabled=\"true\" android:exported=\"false\">\n <\/service>\n"]
Falls die Dateien zum Bearbeiten aus dem ZIP-Archiv extrahiert wurden, müssen sie nun wieder in das Archiv (die .aix-Datei) einkopiert werden.
Für die Erstellung eigener Extensions habe ich einige Tipps zusammengestellt: AI2 FAQ: Extensions entwickeln.