Intent
Communication par Intent
- Message de communication de haut niveau définie dans l'API Java pour communiquer une action d'un composant à un autre
-
Types de transmissions d'Intent :
- Unicast : vers un composant explicitement nommé (classe Java)
- Anycast : vers un composant assurant une certaine action (visualisation, édition...) sur un certain type de données
- Multicast : diffusion vers des récepteurs de broadcast inscrits pour recevoir un type d'Intent
- Permet une communication unidirectionnelle ou bidirectionnelle (Intent réponse à un Intent demande)
Principales utilisation d'un Intent depuis une activité, un service ou un BroadcastReceiver (composants héritant de Context) :
- Lancement d'une activité : startActivity(Intent intent)
- Démarrage d'un service : startService(Intent intent)
- Envoi d'un message en broadcast : sendBroadcast(Intent intent)
Structure d'un Intent
-
Elements constitutifs :
-
Action à réaliser : setAction(String actionName)
- constantes ACTION_* dans Intent pour les actions systèmes
- Donnée sur laquelle réaliser l'action sous forme d'URI (adresse locale ou distante) : setData(Uri uri)
- Paramètres additionnels (EXTRA) : putExtra(String name, {int, float, String, Serializable...} value)
-
Action à réaliser : setAction(String actionName)
-
L'Intent est Parcelable (procédé de sérialisation propre à Android) pour être envoyé entre binders
- ☞ Ne pas incorporer des données trop volumineuses avec putExtra dans l'Intent, l'usage d'une URI est préférable
-
Création d'un Intent :
- new Intent(String action, Uri uri) pour appel implicite
- new Intent(Context c, Class<?> cls) pour appel explicite ; pour un appel inter-application, on peut utiliser setClassName(String package, String className)
- Ajout de catégories avec addCategory(String category) pou appel implicite : un composant doit valider toutes les catégories pour recevoir l'Intent
- Ajout de drapeaux (constantes entières combinables avec |) avec setFlags(int flags)
Actions d'Intent pour activités
- Action : constante String définissant une action à réaliser par le récepteur de l'Intent
-
Possibilité de créer ses propres actions ; une action devrait être une chaîne unique (préfixage par le nom complet de la classe conseillé) :
- public static final String ACTION_CUSTOM = MyClass.class.getName() + ".custom";
Quelques constantes d'actions usuelles définies dans la classe Intent :
ACTION_MAIN | action principale |
ACTION_VIEW | visualisation de données |
ACTION_ATTACH_DATA | attachement de données sur une URI |
ACTION_EDIT | éditer des données |
ACTION_PICK | choisir sur un répertoire de données |
ACTION_CHOOSER | force l'apparition d'un menu pour choisir l'application à utiliser ; EXTRA_INTENT contient l'Intent originel, EXTRA_TITLE le titre du menu |
ACTION_GET_CONTENT | obtenir un contenu selon son type MIME |
ACTION_SEND | envoyer un contenu spécifié par EXTRA_TEXT ou EXTRA_STREAM à un destinataire non spécifié |
ACTION_SENDTO | envoyer un contenu à un destinataire spécifié dans l'URI |
ACTION_INSERT | insère un élément vierge dans le répertoire spécifié par l'URI |
ACTION_DELETE | supprime l'élément désigné par l'URI |
ACTION_PICK_ACTIVITY | propose un menu de sélection d'activité selon l'EXTRA_INTENT fourni ; retourne la classe choisie mais ne la lance pas |
ACTION_SEARCH, ACTION_WEBSEARCH | réalise une recherche (chaîne à rechercher avec clé SearchManager.QUERY) |
...
Possibilité de retrouver toutes les activités pouvant traiter un Intent implicite avec getPackageManager().queryIntentActivities(intent).
Exemple de méthode transformant un Intent implicite en une Map<String, Intent> avec tous les Intent explicitant les activités :
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License package fr.upem.coursand.filechooser import android.content.Context import android.content.Intent import android.content.pm.PackageManager /** * This extension method for an implicit Intent find all the activities that are compatible * with the given Intent and create a map for all these activities with the Intent to launch them. * * Typical use to obtain all the activities for ACTION_SEND compatible with image/jpeg * val intentMap = Intent(android.content.Intent.ACTION_SEND).setType("image/jpeg").resolveIntent() */ fun Intent.resolveIntent(context: Context): Map<String, Intent> { // get a List<ResolveInfo> val resolveInfos = context.packageManager.queryIntentActivities(this, PackageManager.MATCH_ALL) return resolveInfos.map { val intent = this.clone() as Intent // set the class name for the Intent intent.setClassName(it.activityInfo.packageName, it.activityInfo.name) // return the label of the activity linked to the Intent to launch it it.activityInfo.name to intent }.sortedBy { it.first }.toMap() }
Quelques drapeaux d'Intent utilisables avec setFlags(int)
-
Autorisation temporaire sur données :
- FLAG_GRANT_READ_URI_PERMISSION : demande une autorisation temporaire en lecture
- FLAG_GRANT_WRITE_URI_PERMISSION : demande une autorisation temporaire en écriture
-
Drapeaux pour activités :
- FLAG_ACTIVITY_CLEAR_TASK : activité seule dans une tâche
- FLAG_ACTIVITY_CLEAR_TOP : si l'activité tourne dans la tâche courante, elle est mise en haut de pile en supprimant les activités au-dessus
- FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET : marque un point de retour dans la pile d'activités
- FLAG_ACTIVITY_NEW_TASK : créé une nouvelle tâche pour l'activité
- FLAG_ACTIVITY_REORDER_TO_FRONT : ramène l'activité en haut de pile de tâche si elle est déjà lancée
- FLAG_ACTIVITY_SINGLE_TOP : pas de lancement si l'activité est déjà en haut de pile
- ...
-
Drapeau pour récepteur de broadcast :
- FLAG_RECEIVER_REGISTERED_ONLY : cible uniquement les récepteurs enregistrées (pas de lancement de récepteur déclaré dans le manifeste)
Démarrage d'activité (et communication inter-activité)
Principe
-
Démarrage d'une activité sans retour d'information :
- startActivity(Intent intent)
-
Démarrage d'une activité avec retour d'information :
-
Avec Java :
-
startActivityForResult(Intent intent, int requestCode)
- Il faut redéfinir onActivityResult(int requestCode, int resultCode, Intent data) pour récupérer le résultat
-
startActivityForResult(Intent intent, int requestCode)
-
Avec Kotlin :
- private val getResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> Log.i("result intent", result.data); }
- Lancement avec getResult.launch(Intent(this, ActivityToLaunch::class.java))
-
Avec Java :
- Si l'activité n'est pas trouvée (Intent implicite) : ActivityNotFoundException
-
Au niveau de l'activité enfant démarrée :
- Récupération de l'Intent de démarrage avec getIntent()
-
Spécification du résultat avec setResult(int resultCode, Intent data)
- typiquement resultCode est RESULT_CANCELED ou RESULT_OK
- il est possible d'incorporer des paramètres extra sur le data
-
Protection contre le lancement intempestif d'activités depuis Android 10
- Lancement impossible depuis un service en arrière-plan
- Une interaction récente préalable avec le système ou l'utilisateur est nécessaire (lancement depuis une tâche en avant-plan, réception récente d'un PendingItent...)
- Notification préférable ou lancement d'activité pour les services en arrière-plan
Exemple : activité de login
Une activité a besoin de connaître l'identifiant et le mot de passe de l'utilisateur pour un service web.
Création de l'activité de connexion appelante
L'activité appelante ConnectionActivity peut communiquer le nom du service de connexion à LoginActivity. LoginActivity est chargée de récupérer les identifiants de l'utilisateur (et peut récupérer des identifiants déjà mémorisés dans les préférences).
En Java, on utilise startActivityForResult :
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License package fr.upem.coursand.login; import android.content.Intent; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.Button; import android.widget.TextView; import fr.upem.coursand.R; /** A fake connection activity that uses the {@link fr.upem.coursand.login.LoginActivity} * to retrieve login information and display them. * This is an example using startActivityForResult. */ public class ConnectionActivity extends AppCompatActivity { public static final String SERVICE_NAME = "coursandService"; public static final int REQUEST_ID = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_connection); Button supplyLoginButton = findViewById(R.id.supplyLoginButton); supplyLoginButton.setOnClickListener( v -> { Intent i = new Intent(this, LoginActivity.class); i.putExtra("service", SERVICE_NAME); startActivityForResult(i, REQUEST_ID); }); } /** Called back when we have the result of {@link fr.upem.coursand.login.LoginActivity} */ @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); TextView v = findViewById(R.id.loginInfoView); if (requestCode == REQUEST_ID && resultCode == RESULT_OK) { String text = "Fetched login data: \n" + "Username: " + data.getStringExtra("username") + "\n" + "Password: " + data.getStringExtra("password") + "\n"; v.setText(text); } else if (requestCode == REQUEST_ID && resultCode == RESULT_CANCELED) { v.setText("cancelled"); } } }
Avec Kotlin, startActivityForResult est déprécié, on utilise registerForActivityResult qu'on initialise avec un ActivityResultContract :
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License package fr.upem.coursand.login import android.content.Intent import android.os.Bundle import android.view.View import android.widget.Button import android.widget.TextView import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.registerForActivityResult import androidx.appcompat.app.AppCompatActivity import fr.upem.coursand.R /** A fake connection activity that uses the [KotlinLoginActivity] * to retrieve login information and display them. * This is an example using startActivityForResult. */ class KotlinConnectionActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_connection) val supplyLoginButton = findViewById<Button>(R.id.supplyLoginButton) supplyLoginButton.setOnClickListener { v: View? -> val i = Intent(this, LoginActivity::class.java) i.putExtra("service", SERVICE_NAME) getResult.launch(i) } } private val getResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { val v = findViewById<TextView>(R.id.loginInfoView) if (it.resultCode == RESULT_OK) { it.data?.let { data -> val text = """ Fetched login data: Username: ${data.getStringExtra("username")} Password: ${data.getStringExtra("password")} """.trimIndent() v.text = text } } else if (it.resultCode == RESULT_CANCELED) { v.text = "cancelled" } } companion object { const val SERVICE_NAME = "coursandService" } }
Création de l'activité de récupération des identifiants (activité appelée)
L'activité appelée se charge de demander à l'utilisateur ses identifiants et les envoie comme résultat à l'activité appelante. Si les identifiants sont présents dans les préférences, ils sont préremplis.
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License package fr.upem.coursand.login; import android.content.Intent; import android.content.SharedPreferences; import android.preference.PreferenceManager; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.CheckBox; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import fr.upem.coursand.R; /** An activity that asks the user to supply her login information. * The username and password are then sent back to the calling activity. * It illustrates bidirectional communication between activities. */ public class LoginActivity extends AppCompatActivity { private SharedPreferences prefs; private String service; // the service for which we login private EditText usernameView; private EditText passwordView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); usernameView = findViewById(R.id.usernameView); passwordView = findViewById(R.id.passwordView); // read the calling intent service = getIntent().getStringExtra("service"); TextView serviceView = findViewById(R.id.serviceView); serviceView.setText(service); // display the service name // maybe we have already saved into the preferences of the activity the login info for this service // in this case we prefill the input fields with the stored data prefs = PreferenceManager.getDefaultSharedPreferences(this); String username0 = prefs.getString(service + ".username", null); String password0 = prefs.getString(service + ".password", null); if (username0 != null) usernameView.setText(username0); if (password0 != null) passwordView.setText(password0); // set the login info findViewById(R.id.loginButton).setOnClickListener( v -> { String username = usernameView.getText().toString(); String password = passwordView.getText().toString(); if (username.isEmpty() || password.isEmpty()) { Toast.makeText(this, "Missing information", Toast.LENGTH_LONG).show(); } else { CheckBox rememberCheckBox = findViewById(R.id.rememberCheckBox); SharedPreferences.Editor editor = prefs.edit(); // open a transaction for the preferences if (rememberCheckBox.isChecked()) { // we store the login info into the prefs editor.putString(service + ".username", username); editor.putString(service + ".password", password); editor.putLong(service + ".timestamp", System.currentTimeMillis()); } else { // we must forget the information already (or not) stored editor.remove(service + ".username"); editor.remove(service + ".password"); editor.remove(service + ".timestamp"); } // finally we commit the prefs transaction editor.commit(); // create a result intent and send it back to the calling activity Intent resultIntent = new Intent(); resultIntent.putExtra("service", service); resultIntent.putExtra("username", username); resultIntent.putExtra("password", password); setResult(RESULT_OK, resultIntent); // the job is done, we quit the activity finish(); } }); } }
Ouverture d'activité depuis une URL
- Possibilité d'ouvrir directement une activité Android depuis une URL web (depuis le navigateur, un client mail...)
-
Résumé des étapes nécessaires (plus détaillées ici) :
- Ajout d'un <intent-filter>...</intent-filter> dans la définition de l'activité dans le manifeste en indiquant le ou les domaines pouvant lier vers l'activité
- Ajout d'un fichier JSON accessible depuis https://domain/.well-known/assetlinks.json pour autoriser le domaine à lier vers l'application (avec indication de l'ID de l'application et de l'empreinte SHA256 du certificat signant l'application)
BroadcastReceiver
- Système de bus de communication global permettant de communiquer des événements entres applications et système
- Concept présentant des similarités avec DBus utilisé sur les systèmes Linux de bureau
-
Deux opérations possibles :
- Emission d'un événement broadcast (exprimé sous la forme d'un Intent) avec sendBrodcast()
- Réceptions d'événements broadcast filtrés avec un BroadcastReceiver
Principe de fonctionnement d'un BroadcastReceiver
- Composant d'application pour la réception d'Intent diffusé
- Méthode void onReceive(Context context, Intent intent) pour traiter l'Intent diffusé
-
Création et destruction de BroadcastReceiver :
-
Automatique à la diffusion d'un Intent intéressant si déclaration par balise <receiver> dans le manifeste
- Certains événements de survenue fréquente ne peuvent pas être ainsi gérés
- Ne permet pas de se connecter à un service (uniquement de lancer un service)
-
Manuelle en l'initialisant et l'enregistrant (et le désenregistrant) dans la thread principale de l'application (depuis un Context comme une activité) avec :
- Intent registerReceiver(BroadcastReceiver br, IntentFilter filter)
- L'Intent éventuellement retourné est un sticky broadcast vérifiant le filtre
- Version longue : registerReceiver(BroadcastReceiver br, IntentFilter filter, String permission, Handler scheduler) pour indiquer une permission à vérifier pour l'émetteur ainsi qu'un Handler personnalisé
- void unregisterReceiver(BroadcastReceiver br) permet de révoquer l'enregistrement du receiver (lorsque l'on a plus besoin de recevoir des événements : pour les activités, typiquement dans onPause())
-
Automatique à la diffusion d'un Intent intéressant si déclaration par balise <receiver> dans le manifeste
Événements broadcast du système
Listés comme constantes dans Intent
- ACTION_TIME_TICK : émis toutes les minutes
- ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED : remise à l'heure, changement de fuseau horaire
- ACTION_BOOT_COMPLETED : fin de la séquence de démarrage, utile pour démarrer des services démons
- ACTION_PACKAGE_{ADDED, CHANGED, REMOVED, RESTARTED, DATA_CLEARED} : événements concernant des modifications sur les applications (EXTRA_UID fournit le user ID)
- ACTION_UID_REMOVED : suppression d'un user ID (EXTRA_UID fourni)
- ACTION_BATTERY_CHANGED : broadcast sticky informant sur l'état de la batterie, ne peut être reçu par déclaration dans le manifeste
- ACTION_POWER_{CONNECTED, DISCONNECTED} : état de chargement secteur
- ACTION_SHUTDOWN : extinction du système
- ...
Exemple 1 : Une gauge à batterie recevant ACTION_BATTERY_CHANGED
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License package fr.upem.coursand.battery; import android.app.ActionBar; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.BatteryManager; import android.os.Bundle; import android.widget.TextView; import java.util.Locale; import fr.upem.coursand.R; /** * This class presents the use of a BroadcastReceiver to receive a sticky broadcast * in order to display the status of the device battery. */ public class BatteryActivity extends Activity { private final BroadcastReceiver batteryReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Fetch battery state data in the intent updateStatus(String.format(Locale.getDefault(), "Charge ratio: %f\nBattery technology: %s\nBattery temperature: %s\nBattery voltage: %d\n", (float)intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1), // charge ratio intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY), intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1), intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1)), intent.getIntExtra(BatteryManager.EXTRA_ICON_SMALL, -1)); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_battery); } private void updateStatus(String text, int logo) { TextView view = findViewById(R.id.mainBatteryTextView); view.setText(text); ActionBar ab = getActionBar(); if (ab != null) ab.setLogo(logo); // Set an icon for the battery state } @Override protected void onStart() { super.onStart(); registerReceiver(batteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); } @Override protected void onStop() { super.onStop(); unregisterReceiver(batteryReceiver); } }

Types d'événements broadcast
Broadcast normal/ordonné
- Normal : broadcast diffusé simultanément à tous les récepteurs, l'ordre de réception est indéterminé
- Ordonné : broadcast propagé à une chaîne de récepteurs organisée par priorité décroissante ; le récepteur de plus forte priorité est le premier de la chaîne et peut décider de le propager (avec possible modification de l'Intent) ou non au prochain récepteur de la chaîne, et ainsi de suite
Broadcast non collant/collant
- Non collant : le récepteur doit être enregistré avant l'émission du broadcast
- Collant : l'enregistrement du récepteur permet d'obtenir un broadcast collant envoyé antérieurement
Envoi d'un événement broadcast
- L'envoi d'un broadcast est asynchrone (la méthode d'envoi retourne immédiatement sans attendre le traitement du broadcast par les récepteurs)
- Utilisation de méthodes sur Context (Activity, Service)
-
Émission de broadcast non-collant :
- sendBroadcast(Intent intent, String permission)
- sendOrderedBroadcast(Intent intent, String permission)
-
Émission de broadcast collant (nécessite la permission BROADCAST_STICKY) :
- sendStickyBroadcast(Intent intent)
- sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver finalReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)
-
Suppression d'un broadcast collant :
- removeStickyBroadcast(Intent intent)
-
Par défaut un broadcast ne peut réveiller une application dans un état arrêté
- Une application est dans un état arrếté après son installation ou sur demande explicite de l'utilisateur (Configuration→Applications)
- Ajout nécessaire du flag FLAG_INCLUDE_STOPPED_PACKAGES sur l'intent du broadcast pour réveiller l'application
Broadcast local
- LocalBroadcastManager permet de gérer des événements broadcast localement dans un processus (évite le coût de la communication inter-processus)
-
Méthodes utiles :
- Méthode statique d'obtention : LocalBroadcastManager.getInstance(Context context)
-
Méthodes classiques :
- registerReceiver(BroadcastReceiver receiver, IntentFilter filter)
- unregisterReceiver(BroadcastReceiver receiver)
- sendBroadcast(Intent intent)
-
Envoi synchrone d'un broadcast :
- sendBroadcastSync(Intent intent) : retourne une fois l'exécution des méthodes onReceive() des BroadcastReceiver achevée
Déclaration d'un BroadcastReceiver dans le manifeste
-
Déclaration permettant de recevoir un Intent :
- Validant un filtre spécifié
- Ou adressé directement depuis une autre application au BroadcastReceiver
<receiver android:enabled=["true" | "false"] ← Instantiation automatique par le système android:exported=["true" | "false"] ← Accessibilité depuis les autres applications android:icon="drawable resource" android:label="string resource" android:name="string" ← Nom de la classe android:permission="string" ← Permission nécessaire pour envoyer un broadcast android:process="string" > . . . </receiver>
Filtre d'Intent
-
Définissable dynamiquement avec IntentFilter :
- new IntentFilter(String action[, String dataType])
-
Spécifiable dans le manifeste avec balises <intent-filter> dans <activity>, <service> ou <receiver>
- <action android:name="nom.de.l.action" />
- <category android:name="category" />
- Quelques catégories : android.intent.category.{DEFAULT, BROWSABLE, TAB, ALTERNATIVE, LAUNCHER, HOME, PREFERENCE, TEST, ...}
- <data android:mimeType="typeMIME" ... />
Quelques exemples de filtre
Pour qu'une activité soit affichée dans le launcher d'applications :
<intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter>
Pour recevoir les intent implicites demandant à visualiser ou éditer un fichier JPEG :
<intent-filter android:label="@string/jpeg_editor"> <action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.EDIT" /> <data android:mimeType="image/jpeg" /> </intent-filter>
Intents implicites
-
Intent implicite défini par :
- une action
- des catégories (addCategory(String))
- et une donnée (setData(Uri) pour l'URI, setType(String) pour le type MIME)
-
Maintien d'un catalogue de composants associés à leur IntentFilter :
-
Le PackageManager cherche les composants traitant l'Intent implicite fourni (avec respect des permissions) ;
- on peut demander manuellement Package.query{IntentActivities, IntentServices, BroadcastReceivers, ContentProviders}(Intent intent, int flags)
-
Le PackageManager cherche les composants traitant l'Intent implicite fourni (avec respect des permissions) ;
-
Si plusieurs composants compatibles :
- pour un broadcast normal, l'Intent est envoyé à tous les récepteurs
- pour un broadcast ordonné, l'Intent est envoyé au récepteur de plus forte priorité
- pour un Intent de démarrage d'activité, l'Intent est délivré au composant de plus forte priorité
- pour démarrer une activité, il est possible d'afficher un popup de choix avec Intent.createChooser(Intent target, String title)
- Un service ne peut être démarré par un Intent implicite (Intent explicite obligatoire avec indication au minimum du paquetage de réception)
Exemple : une activité calculant les empreintes cryptographiques MD5, SHA-1, SHA-2... d'un fichier
-
Développement de l'activité
- Récupération de l'Intent de lancement par l'activité avec getIntent()
-
Récupération de l'URI du fichier dont les empreintes doivent être calculées avec ``getIntent().getParcelableExtra(Intent.EXTRA_STREAM)`
- Seule l'URI du fichier est communiquée dans l'Intent, pas son contenu (sérialisation trop coûteuse)
- Ouverture d'un InputStream pour lire les données pointées par l'URI avec getContentResolver().openInputStream(uri)
- Utilisation de MessageDigest de l'API java.security pour calculer les empreintes
-
Déclaration dams le manifeste
- Activité avec <intent-filter> capturant les Intent avec l'action SEND et n'importe quel type MIME de données
-
Utilisation
- Emploi du bouton partager disponible dans de nombreuses activités
- Lancement d'un Intent implicite avec l'action ACTION_SEND : le système propose de choisir parmi toutes les activités capturant SEND_TO dont les activités de messagerie, de réseaux sociaux... et notre activité de génération d'empreintes
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License package fr.upem.coursand.digester import android.content.Intent import android.net.Uri import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import fr.upem.coursand.R import kotlinx.android.synthetic.main.activity_digester.* fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } class DigesterActivity : AppCompatActivity() { var uri: Uri? = null private var digester: Digester? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_digester) uri = intent.data // usually the Uri is in the extra variable EXTRA_STREAM if (uri == null) uri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri } override fun onStart() { super.onStart() val uri = this.uri if (uri != null) { // create an async task for digest computation digester = object : Digester() { override fun onPreExecute() { uriView.text = uri.toString() // display the metadata about the file resultView.text = uri.getMetadata(this@DigesterActivity)?.map { "${it.key}=${it.value}" }?.joinToString("\n") } override fun onProgressUpdate(vararg values: Long?) { // update the byte counter statusView.text = "${values.last()} bytes processed" } override fun onPostExecute(result: Map<String, ByteArray>?) { // display the result if (result != null) { val resultText = result.map { "${it.key}=${it.value.toHexString()}" }.joinToString("\n") resultView.append(resultText) } else resultView.append("computation aborted") } } // start asynchronously the computation with the digester digester?.execute(contentResolver.openInputStream(uri)) } else uriView.text = "This activity must be started with an Intent including a data URI" } // destroy the digest when the activity is destroyed // (we continue the computation if the activity is alive in the background) override fun onDestroy() { super.onDestroy() digester?.cancel(true) } }
AlarmManager
-
L'AlarmManager permet de planifier l'envoi d'un Intent dans le futur
- Pour l'envoi d'un Intent dans le passé, ce produit est recommandé
- L'intent planifié peut déclencher le démarrage d'un service ou l'envoi d'un message en broadcast
-
On confie l'Intent à exécuter à l'AlarmManager en crééant un PendingIntent :
- Il s'agit d'une délégation d'exécution de l'Intent avec transfert des permissions possédées
-
Création avec get{Activity, Service, Broadcast}(Context context, int requestCode, Intent intent, int flags)
- Drapeaux utilisables : FLAG_ONE_SHOT pour un usage unique du PendingIntent, FLAG_IMMUTABLE pour interdire l'ajout d'arguments additionels, FLAG_CANCEL_CURRENT pour annuler un éventuel PendingIntent de même nature, FLAG_UPDATE_CURRENT pour mettre à jour un éventuel PendingIntent identique plutôt que d'en créer un nouveau
- Un PendingIntent est annulable par l'application qui l'a créé avec la méthode cancel()
-
La récupération de l'AlarmManager est réalisée comme tout autre service système :
- AlarmManager am = (AlarmManager)Context.getSystemService(Context.ALARM_SERVICE)
-
On choisit le type d'alarme :
- En utilisant l'horloge "wall clock" (temps conventionnel humain) : RTC
- En utilisant l'horloge en temps réel écoulé (non sensible aux mises à jour de la "wall clock") : ELAPSED_REALTIME
- Pour RTC et ELAPSED_REALTIME, il existe des versions WAKEKUP qui réveillent l'appareil même s'il est en veille (à utiliser avec parcimonie) ; par défaut l'appareil n'est pas réveillé
-
On ajoute une alarme avec une de ces méthodes :
- set(int type, long triggerAtMillis, PendingIntent operation)
- setExact (int type, long triggerAtMillis, PendingIntent operation) : pour ajouter une alarme précise (autorisé uniquement pour les applications disposant de la permission USE_EXACT_ALARM depuis Android 13)
- setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation)
- Il existe une méthode ``setExact
- Une alarme est annulable avec void cancel(PendingIntent pi)
Remarques :
-
Une alarme ne survit pas au redémarrage
- Pour implanter ce comportement, il est nécessaire que l'application sérialise la description des tâches dans un fichier avant l'arrêt puis les replanifie si nécessaire au démarrage (suite au brodcast BOOT_COMPLETED)
- L'AlarmManager ne doit pas être utilisé pour des tâches à court terme (quelques secondes) ; on privilégiera dans ce cas l'usage d'un Handler associé à la thread principale (si la tâche est d'exécution rapide) avec la méthode postDelayed(Runnable r, long delayMillis).
- Pour la planification de tâches sans échéance ferme (tâches de traitement de données, de synchronisation)..., il est recommandé d'utiliser l'API de plus haut niveau WorkManager.
Exemple : une activité qui planifie dans le futur le démarrage d'un service d'affichage de toast
- Bouton créeant un PendingIntent et le planifiant avec l'AlarmManager
- Bouton d'annulation de la planification
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License package fr.upem.coursand.toastservice import android.app.AlarmManager import android.app.PendingIntent import android.content.Context import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.os.SystemClock import fr.upem.coursand.R import kotlinx.android.synthetic.main.activity_future_toast.* /** * Schedule a toast in the future * We use a Intent that starts the ToastService in the future * This Intent will be launched using the AlarmManager */ class FutureToastActivity : AppCompatActivity() { private var currentPendingIntent: PendingIntent? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_future_toast) scheduleButton.setOnClickListener { // get the alarm manager val am = getSystemService(Context.ALARM_SERVICE) as AlarmManager // retrieve the data supplied in the fields val delay = delayView.text.toString().toInt() * 1000 // delay in millis val message = messageView.text.toString() // create the Intent to launch val intent = Intent(this, ToastService::class.java) intent.action = ToastService.TOAST_ACTION intent.putExtra("message", message) // wrap the Intent into a PendingIntent (delegated Intent that will be sent in the future) currentPendingIntent = PendingIntent.getService(this, 0, intent, 0) // schedule the intent with the AlarmManager am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delay, currentPendingIntent) cancelButton.isEnabled = true } cancelButton.setOnClickListener { currentPendingIntent?.let { val am = getSystemService(Context.ALARM_SERVICE) as AlarmManager // cancel the scheduled PendingIntent // note that the PendingIntent may have already been sent am.cancel(it) cancelButton.isEnabled = false } } } }