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

One does not simply: migrating to Android 12 🤯

One does not simply: migrating to Android 12 🤯

A not so easy migration to support Android 12! This talk will show you all the subtleties you need to know before migrating your applications to Android 12.

From the Manifest to PendingIntent, through the new restrictions on the Bluetooth/Telephony side or even in terms of security and privacy, Android 12 brings a lot of changes. We'll explore how we can achieve the support of this new Android version and see which tools can facilitate the migration. On top of that, we'll have a look at the some of the new APIs that arrived with Android 12.

One of the most laborious migrations since Android 6 and the introduction of runtime permissions 😆 3, 2, 1 let's do it!

56047a7b11797f42c2e7030d771fe803?s=128

Julien Salvi

June 02, 2022
Tweet

More Decks by Julien Salvi

Other Decks in Programming

Transcript

  1. migrating to Android Julien Salvi - Android GDE | Lead

    Android Engineer @ Aircall @JulienSalvi One does not simply:
  2. Julien Salvi Lead Android Engineer @ Aircall Android GDE PAUG,

    Punk & IPAs! @JulienSalvi Hello!
  3. ⚠ Disclaimer ⚠ • Android 12 • Migrate to a

    new Android version • API changes • Tricks & tips to successfully migrate your app • Go deep in the implementation • Lord of the Rings 😅
  4. Discovering Android New version, new life!

  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 is stable 🥳 ! New API level requirements 1st 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. Personal Introduces Material 3 and Material You New design for

    components Theme and color can be personalized New API to build widgets SplashScreen API for all apps
  10. None
  11. Safe Indicator for the microphone and the camera

  12. Safe Indicator for the microphone and the camera Share your

    location: precise or approximate
  13. Safe Indicator for the microphone and the camera Share your

    location: precise or approximate Bluetooth Permission at runtime! List of permissions used by your apps Permissions are removed for apps that have not been used for a while
  14. well… not so simple for the developers!

  15. Story of a migration… …and one does not simply migration

    to Android 12
  16. Is it that complicated? Really? - Me now

  17. So what do we need check at first? - me

    again right now
  18. 💡Tip 1 Documentation 📚

  19. None
  20. Gradle configuration • Update the gradle build files and target

    the API 31 • Update the build tools • Let’s compile… 💥💥💥💥💥💥💥 android { compileSdkVersion 31 buildToolsVersion "31.0.0" defaultConfig { targetSdkVersion 31 } }
  21. None
  22. Manifest configuration • For Activity, Service et BroadcastReceiver which use

    IntentFilter, the field android:exported must be defined for security reasons. • Let’s 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 • Same error but with an external library.

    • Let’s check if there is any updates, otherwise we update the manifest. • Let’s 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 • Every compilation will tell which component has

    not been exported! • Gradle to the rescue to find the missing components! 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 https://bit.ly/3M9Nw4q application.childNodes.each { child -> def childNodeName =

    child.nodeName if (childNodeName == "activity" || childNodeName == "activity-alias" || childNodeName == "service" || childNodeName == "receiver" ) { ... } } • Every compilation will tell which component has not been exported! • Gradle to the rescue to find the missing components!
  26. Manifest configuration 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" ... } } • Every compilation will tell which component has not been exported! • Gradle to the rescue to find the missing components!
  27. Manifest configuration • Every compilation will tell which component has

    not been exported! • Gradle to the rescue to find the missing components! • Let’s 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 After using the app for a while… 💥 🤔(ok something’s missing)
  29. PendingIntent mutability • Important changes for the Notifications. • A

    mutability flag now must be specified to properly handle notification PendingIntent. 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 ) Available since Android 12
  30. PendingIntent mutability • Update to the latest WorkManager stable version!

    It uses PendingIntent. • And check other 3rd party libraries which can affect your apps! 💥🥲 dependencies { def work_version = "2.7.1" // Kotlin + coroutines implementation "androidx.work:work-runtime-ktx:$work_version" }
  31. System dialogs • Before Android 12, ability to close system

    dialogs but you get a warning in the Logcat • From Android 12, calling this function will throw a 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. Don’t be afraid to run Lint analysis 💡Half way Tip

  33. Bluetooth • 2 new permissions to manage Bluetooth! • Major

    change: Bluetooth permissions are now runtime! • Runtime… so now we must ask the users while using the app… 😅 <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. Bluetooth 🤯 • Context: we want to connect to the

    user bluetooth headset • We use Bluetooth API to get the device name… but not to route the audio to the device 😮 • A bit complex isn’t it?! 😅 👀 Let’s go to Android Studio
  35. Don’t be afraid to explore the Android source code and

    associated libraries 💡New tip!
  36. Subtle but impactful improvements! Android 12 and performances 📊

  37. Trampoline notification • Android 12 restricts the trampoline effect with

    notifications • It’s now forbidden to open an Activity from a Service or a BroadcastReceiver Notification Service / BroadcastReceiver Activity
  38. Trampoline notification // We define a Receiver which can start

    an 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 } ) } } • Android 12 restricts the trampoline effect with notifications • It’s now forbidden to open an Activity from a Service or a BroadcastReceiver
  39. Trampoline notification // Let’s build the notification with the 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. • Android 12 restricts the trampoline effect with notifications • It’s now forbidden to open an Activity from a Service or a BroadcastReceiver
  40. Trampoline notification • Android 12 restricts the trampoline effect with

    notifications • It’s now forbidden to open an Activity from a Service or a BroadcastReceiver • And to fix it? 🤔 val activityIntent = Intent(context, RandomActivity::class.java) // Let’s create our PendingIntent val resultPendingIntent = TaskStackBuilder.create(context).run { addNextIntentWithParentStack(activityIntent) getPendingIntent( 0, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } Trampoline notification ✅
  41. Foreground service • It’s forbidden to launch Services while the

    app is in background • Alternatively, you can use expedited work with 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. Test, Test, Test, Test, Test, Test to avoid surprises 🥲

    💡Last tip ! (and sometimes bad surprises 🙃)
  43. Ladies and gentlemen, the telephony stack 🥁🥁🥁🥁🥁🥁

  44. Telephony stack • Nothing in the migration guide for Android

    12 • No Lint errors when launching the analysis • But some methods are deprecated… 👀 Let’s go to Android Studio
  45. Things to keep in mind while migrating to Android

  46. Key points 🔬Test beta versions as soon as possible to

    prepare the migration ⚠ Beware 3rd party libraries and deprecated methods ! 💾 Go check the Android source code from time to time
  47. Resources 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. ????

  49. Thanks! … and good luck 😅 Julien Salvi - Android

    GDE @JulienSalvi