Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Migrer vers Android 12: plus compliqué que de replier une tente 2s

Migrer vers Android 12: plus compliqué que de replier une tente 2s

Une migration pas facile qu'est celle d'Android 12 ! Ce talk vous comptera toutes les subtilités qu'ils faut connaître avant de migrer votre application vers Android 12.

Du Manifest aux PendingIntent en passant par les nouvelles restrictions du côté du Bluetooth ou encore au niveau de la sécurité et de la vie privée. Android 12 apporte énormément de changements (et certain bien caché !) pour les développeurs. Nous explorerons comment s'occuper de la migration vers cette nouvelle version d'Android et quelques tools qui peuvent nous faciliter la tâche.

Une des migrations les plus laborieuses depuis Android 6 et l'introduction des permissions au runtime 😆3, 2, 1 on replie !

56047a7b11797f42c2e7030d771fe803?s=128

Julien Salvi

April 25, 2022
Tweet

More Decks by Julien Salvi

Other Decks in Programming

Transcript

  1. Migrer vers Android Julien Salvi - Android GDE | Lead

    Android Engineer @ Aircall @JulienSalvi plus compliqué que de replier une tente 2s
  2. Julien Salvi Lead Android Engineer @ Aircall Android GDE PAUG,

    Punk & IPAs! @JulienSalvi Bonjour !
  3. ⚠ 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
  4. A la découverte d’Android 12 Nouvelle version, nouvelle vie!

  5. 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
  6. None
  7. None
  8. None
  9. 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
  10. None
  11. Plus Sécurisé Indicateur pour le microphone et la caméra

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

    de localisation : précise ou approximative
  13. 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
  14. pas si simple pour les développeurs !

  15. Histoire d’une migration… …et on ne migre pas si facilement

    sur Android 12
  16. - Moi à l’instant C’est si compliqué que ça ?

    Vraiment ?
  17. - Encore moi à l’instant Et qu’est-ce qu’on regarde en

    premier ?
  18. 💡Tip 1 La documentation 📚

  19. None
  20. 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 } }
  21. None
  22. 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… 💥💥💥💥💥💥💥 <service android:name=".CallKitConnectionService" android:exported="true" <intent-filter> <action android:name="android.telecom.ConnectionService"/> </intent-filter> </service> <receiver android:name=".MyAlarmBroadcastReceiver" android:exported="false"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver> <activity android:name=".HomeActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity>
  23. 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… 💥💥💥💥💥💥💥 <service android:name="com.ext.lib.fcm.EmptyMessagingService" android:exported="false" tools:node="merge" /> <activity android:name="com.ext.lib.OpenNotificationActivity" android:exported="false" tools:node="merge" />
  24. 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) } } } }
  25. 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" ) { ... } }
  26. 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" ... } }
  27. 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) } } } }
  28. 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)
  29. 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
  30. 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" }
  31. 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.
  32. N’hésitez pas à faire une analyse Lint de votre code

    base 💡Tip à mi-parcours
  33. 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… 😅 <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> override fun requestBluetoothPermission(requestCode: Int) { if (osVersionProvider.isAtLeastS) { requestPermission( requestCode, BLUETOOTH_CONNECT ) } }
  34. 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
  35. N’hésitez pas à explorer le code source d’Android et les

    API dispo 💡Nouveau tip !
  36. Des améliorations subtiles mais impactantes ! Android 12 et performance

    📊
  37. 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
  38. 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 } ) } }
  39. 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.
  40. 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 ✅
  41. 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)
  42. Testez, testez, testez, testez, testez pour éviter les surprises 🥲

    💡Dernier tip ! (et des surprises pas hyper sympa 🙃)
  43. Mesdames et messieurs, la stack téléphonie 🥁🥁🥁🥁🥁🥁

  44. 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
  45. Les choses à retenir pour migrer sur Android 12

  46. 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
  47. 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
  48. Merci ! Julien Salvi - Android GDE @JulienSalvi