Slide 1

Slide 1 text

Migrer vers Android Julien Salvi - Android GDE | Lead Android Engineer @ Aircall @JulienSalvi plus compliqué que de replier une tente 2s

Slide 2

Slide 2 text

Julien Salvi Lead Android Engineer @ Aircall Android GDE PAUG, Punk & IPAs! @JulienSalvi Bonjour !

Slide 3

Slide 3 text

⚠ Disclaimer ⚠ ● Android 12 ● Migration vers une nouvelle version d’Android ● Changements d’API ● Trucs & astuces pour réussir sa migration ● Détail d’implémentation ● De tente 2s

Slide 4

Slide 4 text

A la découverte d’Android 12 Nouvelle version, nouvelle vie!

Slide 5

Slide 5 text

Android 12 roadmap Avril. Jui. Août Oct. 2022 Developer Previews Beta Releases Android 12 migration deadline nov. 2023 Fev. 2021 2023 Android 12 est stable 🥳 ! New API level requirements 1er Nov. source: https://android-developers.googleblog.com/2022/04/expanding-plays-target-level-api-requirements-to-strengthen-user-security.html

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

Personnalisé Introduction de Material 3 et Material You Nouveau design pour les composants Thème et couleurs personnalisés Un rafraîchissement des widgets SplashScreen pour vos applications

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

Plus Sécurisé Indicateur pour le microphone et la caméra

Slide 12

Slide 12 text

Plus Sécurisé Indicateur pour le microphone et la caméra Partage de localisation : précise ou approximative

Slide 13

Slide 13 text

Plus Sécurisé Indicateur pour le microphone et la caméra Partage de localisation : précise ou approximative Permission Bluetooth au runtime ! Liste des permissions utilisées par les apps Suppression des permissions pour les applications non utilisées depuis un certain temps

Slide 14

Slide 14 text

pas si simple pour les développeurs !

Slide 15

Slide 15 text

Histoire d’une migration… …et on ne migre pas si facilement sur Android 12

Slide 16

Slide 16 text

- Moi à l’instant C’est si compliqué que ça ? Vraiment ?

Slide 17

Slide 17 text

- Encore moi à l’instant Et qu’est-ce qu’on regarde en premier ?

Slide 18

Slide 18 text

💡Tip 1 La documentation 📚

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Configuration coté gradle ● Mettre à jour ses fichiers gradle pour cibler l’API 31 ● Mettre à jour les build tools ● On compile… 💥💥💥💥💥💥💥 android { compileSdkVersion 31 buildToolsVersion "31.0.0" defaultConfig { targetSdkVersion 31 } }

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

Manifest configuration ● Pour les Activity, Service et BroadcastReceiver qui utilisent des IntentFilter, le champ android:exported doit être défini pour une sécurité renforcée. ● On compile… 💥💥💥💥💥💥💥

Slide 23

Slide 23 text

Manifest configuration ● Même erreur mais venant d’une dépendance externe. ● On vérifie les MAJ et si rien n’est dispo on modifie notre manifest. ● On compile… 💥💥💥💥💥💥💥

Slide 24

Slide 24 text

Manifest configuration ● Chaque compilation indiquera le composant qui n’est pas exporté ! ● Gradle à la rescousse pour trouver les composants manquant ! https://bit.ly/3M9Nw4q application.childNodes.each { child -> def childNodeName = child.nodeName if (childNodeName == "activity" || childNodeName == "activity-alias" || childNodeName == "service" || childNodeName == "receiver") { def attributes = child.getAttributes() if (attributes.getNamedItem("android:exported") == null) { def intentFilters = child.childNodes.findAll { it.nodeName == "intent-filter" } if (intentFilters.size() > 0) { def exportedAttrAdded = false for (def i = 0; i < intentFilters.size(); i++) { def intentFilter = intentFilters[i] def actions = intentFilter.childNodes.findAll { it.nodeName == "action" } for (def j = 0; j < actions.size(); j++) { def action = actions[j] def actionName = action.getAttributes().getNamedItem("android:name").nodeValue if (actionName == "com.google.firebase.MESSAGING_EVENT") { ((Element) child).setAttribute("android:exported", "false") exportedAttrAdded = true } } } if (!exportedAttrAdded) { ((Element) child).setAttribute("android:exported", "true") } nodesFromDependencies.add(child) } } } }

Slide 25

Slide 25 text

Manifest configuration ● Chaque compilation indiquera le composant qui n’est pas exporté ! ● Gradle à la rescousse pour trouver les composants manquant ! https://bit.ly/3M9Nw4q application.childNodes.each { child -> def childNodeName = child.nodeName if (childNodeName == "activity" || childNodeName == "activity-alias" || childNodeName == "service" || childNodeName == "receiver" ) { ... } }

Slide 26

Slide 26 text

Manifest configuration ● Chaque compilation indiquera le composant qui n’est pas exporté ! ● Gradle à la rescousse pour trouver les composants manquant ! https://bit.ly/3M9Nw4q def attributes = child.getAttributes() if (attributes.getNamedItem("android:exported") == null) { def intentFilters = child.childNodes.findAll { it.nodeName == "intent-filter" } if (intentFilters.size() > 0) { println "found ${childNodeName} ${attributes.getNamedItem("android:name").nodeValue} with intent filters but without android:exported attribute" ... } }

Slide 27

Slide 27 text

Manifest configuration ● Chaque compilation indiquera le composant qui n’est pas exporté ! ● Gradle à la rescousse pour trouver les composants manquant ! ● On compile… ✅✅✅✅✅✅✅ Safer exporting ✅ https://bit.ly/3M9Nw4q application.childNodes.each { child -> def childNodeName = child.nodeName if (childNodeName == "activity" || childNodeName == "activity-alias" || childNodeName == "service" || childNodeName == "receiver") { def attributes = child.getAttributes() if (attributes.getNamedItem("android:exported") == null) { def intentFilters = child.childNodes.findAll { it.nodeName == "intent-filter" } if (intentFilters.size() > 0) { def exportedAttrAdded = false for (def i = 0; i < intentFilters.size(); i++) { def intentFilter = intentFilters[i] def actions = intentFilter.childNodes.findAll { it.nodeName == "action" } for (def j = 0; j < actions.size(); j++) { def action = actions[j] def actionName = action.getAttributes().getNamedItem("android:name").nodeValue if (actionName == "com.google.firebase.MESSAGING_EVENT") { ((Element) child).setAttribute("android:exported", "false") exportedAttrAdded = true } } } if (!exportedAttrAdded) { ((Element) child).setAttribute("android:exported", "true") } nodesFromDependencies.add(child) } } } }

Slide 28

Slide 28 text

java.lang.IllegalArgumentException: com.azerty: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent. Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles Après un petit moment d’utilisation…. 💥 🤔(ok il manque des choses)

Slide 29

Slide 29 text

PendingIntent mutability ● Des changements important pour vos Notifications. ● Un flag de mutabilité doit être spécifié pour le bon fonctionnement des notifications PendingIntent.getActivity( context, randomRequestCode(), this, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) PendingIntent.getBroadcast( context, notificationId, this, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE ) Dispo depuis Android 12

Slide 30

Slide 30 text

PendingIntent mutability ● Mettez à jour votre dépendance à WorkManager ! Qui est liée aux PendingIntent. ● Et vérifiez vos autres lib externes qui peuvent affecter votre app ! 💥🥲 dependencies { def work_version = "2.7.1" // Kotlin + coroutines implementation "androidx.work:work-runtime-ktx:$work_version" }

Slide 31

Slide 31 text

System dialogs ● Avant Android 12, possibilité de fermer les dialogs system mais alerte dans le Logcat ● Avec Android 12, appeler cette méthode déclenche une SecurityException 💥🥲 sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) E ActivityTaskManager Permission Denial: \ android.intent.action.CLOSE_SYSTEM_DIALOGS broadcast from \ com.package.name requires android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS, \ dropping broadcast.

Slide 32

Slide 32 text

N’hésitez pas à faire une analyse Lint de votre code base 💡Tip à mi-parcours

Slide 33

Slide 33 text

Le Bluetooth ● Deux nouvelles permissions pour la gestion du Bluetooth ! ● Changement majeur : la permission Bluetooth devient runtime ! ● Et qui dit runtime, dit demande à l’utilisateur… 😅 override fun requestBluetoothPermission(requestCode: Int) { if (osVersionProvider.isAtLeastS) { requestPermission( requestCode, BLUETOOTH_CONNECT ) } }

Slide 34

Slide 34 text

Le Bluetooth 🤯 ● Contexte : on veut se connecter au casque bluetooth de l’utilisateur ● On utilise les API Bluetooth pour avoir le nom de l’appareil… mais pas pour se router l’audio vers le device 😮 ● Et c’est pas fini ! 😅 👀 Direction Android Studio

Slide 35

Slide 35 text

N’hésitez pas à explorer le code source d’Android et les API dispo 💡Nouveau tip !

Slide 36

Slide 36 text

Des améliorations subtiles mais impactantes ! Android 12 et performance 📊

Slide 37

Slide 37 text

Notification trampoline ● Android 12 restreint l’effet de trampoline via les notifications ● Interdiction d’ouvrir une Activity depuis un Service ou un BroadcastReceiver Notification Service / BroadcastReceiver Activity

Slide 38

Slide 38 text

Notification trampoline ● Android 12 restreint l’effet de trampoline via les notifications ● Interdiction d’ouvrir une Activity depuis un Service ou un BroadcastReceiver // On définie notre Receiver avec le start de l'Activity class NotificationReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { // Trampoline effect context.startActivity( Intent(context, RandomActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK } ) } }

Slide 39

Slide 39 text

Notification trampoline ● Android 12 restreint l’effet de trampoline via les notifications ● Interdiction d’ouvrir une Activity depuis un Service ou un BroadcastReceiver // On crée notre notification avec notre Broadcast val broadcastIntent = Intent(context, NotificationReceiver::class.java) val actionIntent = PendingIntent.getBroadcast( context, 0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.icon) .setContentTitle("Android12") .setContentText("Notification trampoline") .addAction(R.drawable.icon, "Open activity", actionIntent) .build() notificationManager.notify(getUniqueId(), notification) Indirect notification activity start (trampoline) from PACKAGE_NAME, this should be avoided for performance reasons.

Slide 40

Slide 40 text

Notification trampoline ● Android 12 restreint l’effet de trampoline via les notifications ● Interdiction d’ouvrir une Activity depuis un Service ou un BroadcastReceiver ● Et pour corriger ça ? 🤔 val activityIntent = Intent(context, RandomActivity::class.java) // On crée notre PendingIntent val resultPendingIntent = TaskStackBuilder.create(context).run { addNextIntentWithParentStack(activityIntent) getPendingIntent( 0, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } Notification trampoline ✅

Slide 41

Slide 41 text

Foreground service ● Interdiction de lancer des Services lorsque l’app est en background ● En remplacement, allez sur les expedited work avec WorkManager Fatal Exception: android.app.ForegroundServiceStartNotAllowedException startForegroundService() not allowed due to mAllowStartForeground false: service com.helge.droiddashcam/.service.RecorderService // Build the intent for the service val intent = Intent(...) applicationContext.startForegroundService(intent)

Slide 42

Slide 42 text

Testez, testez, testez, testez, testez pour éviter les surprises 🥲 💡Dernier tip ! (et des surprises pas hyper sympa 🙃)

Slide 43

Slide 43 text

Mesdames et messieurs, la stack téléphonie 🥁🥁🥁🥁🥁🥁

Slide 44

Slide 44 text

La stack téléphonie ● Rien dans la documentation de migration sur Android 12 ● Pas d’erreur Lint à l’analyse ● Mais quelques méthodes dépréciées… 👀 Direction Android Studio

Slide 45

Slide 45 text

Les choses à retenir pour migrer sur Android 12

Slide 46

Slide 46 text

Pour résumer 🔬Testez les versions beta dès que possible pour vous préparer à la migration ⚠ Attention aux libraries externes et aux méthode dépréciées ! 💾 Allez regarder le code source d’Android de temps en temps

Slide 47

Slide 47 text

Un peu de lecture Android 12 documentation https://www.android.com/android-12 https://developer.android.com/about/versions/12 Android Code Search https://cs.android.com/ Play’s Target API Requirements https://android-developers.googleblog.com/2022/04/expanding-plays-target-level-api-re quirements-to-strengthen-user-security.html

Slide 48

Slide 48 text

Merci ! Julien Salvi - Android GDE @JulienSalvi