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

Quoi de neuf avec Android ?

Quoi de neuf avec Android ?

Mis à jour dans Android 11, les outils, et les bibliothèques

Chuck Greb

June 20, 2020
Tweet

More Decks by Chuck Greb

Other Decks in Technology

Transcript

  1. GDG Location GDG Ratoma Quoi de neuf avec Android ?

    Charles “Chuck” Greb Co-lead GDG Ratoma @ecgreb
  2. GDG Location GDG Ratoma Alors … quoi de neuf? Quoi

    de neuf de «Quoi de Neuf avec Android?» Android 11 Outils Bibliothèques
  3. GDG Location GDG Ratoma Window Insets • Plus d'informations sur

    les différents types de contenu affichés ◦ Barre d'état, navigation, IME, ...
  4. GDG Location GDG Ratoma WindowInsets // get WindowInsets object from

    listener view.setOnApplyWindowInsetsListener { view, insets -> }
  5. GDG Location GDG Ratoma WindowInsets // get WindowInsets object from

    listener view.setOnApplyWindowInsetsListener { view, insets -> // See if the IME is visible val imeVisible = insets.isVisible((WindowInsets.Type.ime())) }
  6. GDG Location GDG Ratoma WindowInsets // get WindowInsets object from

    listener view.setOnApplyWindowInsetsListener { view, insets -> // See if the IME is visible val imeVisible = insets.isVisible((WindowInsets.Type.ime())) if (imeVisible) { val imeInsets = insets.getInsets(WindowInsets.Type.ime()) // ... } }
  7. GDG Location GDG Ratoma IME Animations • Synchroniser les animations

    du clavier avec les modifications du contenu de l'application ◦ Écouter les changements ▪ ET / OU ◦ Conduire directement l'animation du clavier logiciel
  8. GDG Location GDG Ratoma editText.setWindowInsetsAnimationCallback(animCallback) val animCallback = object :

    WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { override fun onProgress(p0: WindowInsets, p1: MutableList<WindowInsetsAnimation> ): WindowInsets { ... } // Optional overrides override fun onPrepare(animation: WindowInsetsAnimation) { ... } override fun onEnd(animation: WindowInsetsAnimation) { ... } override fun onStart(animation: WindowInsetsAnimation, bounds: WindowInsetsAnimation.Bounds ): WindowInsetsAnimation.Bounds { ... } } IME Animations En écoutant des modifications du clavier
  9. GDG Location GDG Ratoma IME Animations editText.windowInsetsController?. controlWindowInsetsAnimation( WindowInsets.Type.ime(), /*

    animate the keyboard */ -1, /* infinite duration */ linearInterpolator, /* linear motion */ cancellationSignal, /* allows cancellation */ animationControlListener /* ready/cancelled/finished */ ) Animation directe du clavier
  10. GDG Location GDG Ratoma Conversations // Create and post shortcut

    val person = Person.Builder().build() val shortcutInfo = ShortcutInfoCompat.Builder(this, "sampleShortcut"). setPerson(person). setLongLived(true). // ... build()
  11. GDG Location GDG Ratoma Conversations // Create and post shortcut

    val person = Person.Builder().build() val shortcutInfo = ShortcutInfoCompat.Builder(this, "sampleShortcut"). setPerson(person). setLongLived(true). // ... build() ShortcutManagerCompat.pushDynamicShortcut(shortcutInfo)
  12. GDG Location GDG Ratoma Conversations // Create and post shortcut

    val person = Person.Builder().build() val shortcutInfo = ShortcutInfoCompat.Builder(this, "sampleShortcut"). setPerson(person). setLongLived(true). // ... build() ShortcutManagerCompat.pushDynamicShortcut(shortcutInfo) // Create notification with shortcut val style = NotificationCompat.MessagingStyle(person). addMessage(...). // ...
  13. GDG Location GDG Ratoma Conversations // Create and post shortcut

    val person = Person.Builder().build() val shortcutInfo = ShortcutInfoCompat.Builder(this, "sampleShortcut"). setPerson(person). setLongLived(true). // ... build() ShortcutManagerCompat.pushDynamicShortcut(shortcutInfo) // Create notification with shortcut val style = NotificationCompat.MessagingStyle(person). addMessage(...). // ... NotificationCompat.Builder(this, "foo"). setShortcutId(shortcutInfo.id). // ... build()
  14. GDG Location GDG Ratoma Bubbles • Notifications qui peuvent s'afficher

    comme des bulles • Android 10 : Option de développeur ◦ Android 11 : Elles sont arrivées • Meilleur que System Alert Window! • Créé avec Notification API ◦ avec plus de metadata ◦ et son propre Activity
  15. GDG Location GDG Ratoma Bubbles : Manifest Manifest: <activity android:name=".bubbles.BubbleActivity"

    android:theme="@style/AppTheme.NoActionBar" android:label="@string/title_activity_bubble" android:resizeableActivity="true" />
  16. GDG Location GDG Ratoma Bubbles : Code // Create Intent

    to launch val intent = Intent(context, BubbleActivity::class.java) val bubbleIntent = PendingIntent.getActivity(context, 0, intent,...)
  17. GDG Location GDG Ratoma Bubbles : Code // Create Intent

    to launch val intent = Intent(context, BubbleActivity::class.java) val bubbleIntent = PendingIntent.getActivity(context, 0, intent,...) // Create metadata val shortcutInfo = ... /* probably already using for notifications */ val bubbleMetadata = Notification.BubbleMetadata.Builder(shortcutInfo.id)
  18. GDG Location GDG Ratoma Bubbles : Code // Create Intent

    to launch val intent = Intent(context, BubbleActivity::class.java) val bubbleIntent = PendingIntent.getActivity(context, 0, intent,...) // Create metadata val shortcutInfo = ... /* probably already using for notifications */ val bubbleMetadata = Notification.BubbleMetadata.Builder(shortcutInfo.id) // Create Notification with metadata val builder: Notification.Builder = Notification.Builder(context, CHANNEL_ID) // ... .setBubbleMetadata(bubbleMetadata) .setCategory(...) .setShortcutId(...)
  19. GDG Location GDG Ratoma Quoi de neuf avec System UI

    Android Samples on Github: user-interface-samples/ BubblesKotlin 140: Bubbles!
  20. GDG Location GDG Ratoma Privacy Android 11 enables state of

    the art privacy and security features, protecting users and their data from access by malicious apps, while simultaneously making access of that data more transparent to the user.
  21. GDG Location GDG Ratoma Data Access Auditing • Listen for

    when user-permission-required data is accessed • Great for large apps or use of external libraries
  22. GDG Location GDG Ratoma • Callbacks invoqués lors de l'accès

    aux données protégé par des permissions d’utilisateur Data Access Auditing
  23. GDG Location GDG Ratoma Data Access Auditing val appOpsCallback =

    object : AppOpsManager.OnOpNotedCallback() { override fun onNoted(syncNotedAppOp: SyncNotedAppOp) { ... } override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) { ... } override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) { ... } }
  24. GDG Location GDG Ratoma Data Access Auditing val appOpsCallback =

    object : AppOpsManager.OnOpNotedCallback() { override fun onNoted(syncNotedAppOp: SyncNotedAppOp) { ... } override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) { ... } override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) { ... } } val appOpsManager = getSystemService(AppOpsManager::class.java) as AppOpsManager appOpsManager.setOnOpNotedCallback(mainExecutor, appOpsCallback)
  25. GDG Location GDG Ratoma • Plus restrictif dans Android 11

    • D’abord, demandez la permission de premier plan • Puis demandez la permission d'arrière-plan ◦ L’utilisateur arrive à Settings Background Location
  26. GDG Location GDG Ratoma • Android 10 : l'attribut de

    manifest Location était obligatoire • Android 11 : les nouveaux attributs de manifest sont obligatoire ◦ Camera ◦ Microphone Foreground Services
  27. GDG Location GDG Ratoma Foreground Service Type <manifest> ... <service

    ... android:foregroundServiceType="camera|microphone" /> ... </manifest>
  28. GDG Location GDG Ratoma But Wait, There’s More! • Restrictions

    sur la visibilité des packages • Scoped storage • Auto-reset permissions
  29. GDG Location GDG Ratoma Crash Reasons Reporting • API a

    interroger pourquoi l’appli s'est plantée ◦ télécharger les rapports
  30. GDG Location GDG Ratoma Crash Reasons Querying // Returns List

    of ApplicationExitInfo val reasonsList = activityManager.getHistoricalProcessExitReasons( packageName, pid /* 0 for all matches */, max /* 0 for all */)
  31. GDG Location GDG Ratoma Crash Reasons Querying // Returns List

    of ApplicationExitInfo val reasonsList = activityManager.getHistoricalProcessExitReasons( packageName, pid /* 0 for all matches */, max /* 0 for all */) for (info in reasonsList) { // Log/store/upload info.reason // REASON_LOW_MEMORY, REASON_CRASH, REASON_ANR, etc. }
  32. GDG Location GDG Ratoma • Android 10 : HWASan ◦

    Memory issue debugging • GWP-ASan ◦ Catches memory issues (for native apps) ◦ On user devices in the field ◦ Low overhead (runtime and memory) ◦ Reports uploaded to Play dashboard GWP-ASan
  33. GDG Location GDG Ratoma GWP-ASan <application android:gwpAsanMode="always"> ... </application> //

    Bad memory access caught by GWP-ASan triggers exit + report *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** Build fingerprint: 'google/sargo/sargo:10/RPP3.200320.009/6360804:userdebug/dev-keys' Revision: 'PVT1.0' ABI: 'arm64' Timestamp: 2020-04-06 18:27:08-0700 pid: 16227, tid: 16227, name: 11.test.gwpasan >>> android11.test.gwpasan <<< uid: 10238 signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x736ad4afe0 Cause: [GWP-ASan]: Use After Free on a 32-byte allocation at 0x736ad4afe0 backtrace: #00 pc 000000000037a090 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::CheckNonHeapValue(char, art::(anonymous namespace)::JniValueType)+448) #01 pc 0000000000378440 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::CheckPossibleHeapValue(art::ScopedObjectAccess&, char, art::(anonymous namespace)::JniValueType)+204) #02 pc 0000000000377bec /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::Check(art::ScopedObjectAccess&, bool, char const*, art::(anonymous namespace)::JniValueType*)+612) #03 pc 000000000036dcf4 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::CheckJNI::NewStringUTF(_JNIEnv*, char const*)+708) #04 pc 000000000000eda4 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (_JNIEnv::NewStringUTF(char const*)+40) #05 pc 000000000000eab8 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+144) #06 pc 000000000000edf8 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44) ... deallocated by thread 16227: #00 pc 0000000000048970 /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::AllocationMetadata::CallSiteInfo::RecordBacktrace(unsigned long (*)(unsigned long*, unsigned long))+80) #01 pc 0000000000048f30 /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::GuardedPoolAllocator::deallocate(void*)+184) #02 pc 000000000000f130 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (std::__ndk1::_DeallocateCaller::__do_call(void*)+20) ... #08 pc 000000000000ed6c /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >::~basic_string()+100) #09 pc 000000000000ea90 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+104) #10 pc 000000000000edf8 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44) ... allocated by thread 16227: #00 pc 0000000000048970 /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::AllocationMetadata::CallSiteInfo::RecordBacktrace(unsigned long (*)(unsigned long*, unsigned long))+80) #01 pc 0000000000048e4c /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::GuardedPoolAllocator::allocate(unsigned long)+368) #02 pc 000000000003b258 /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan_malloc(unsigned long)+132) #03 pc 000000000003bbec /apex/com.android.runtime/lib64/bionic/libc.so (malloc+76) #04 pc 0000000000010414 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (operator new(unsigned long)+24) ... #10 pc 000000000000ea6c /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+68) #11 pc 000000000000edf8 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44) ...
  34. GDG Location GDG Ratoma ADB Incremental • Installations plus rapides

    via command-line • APKs énorme (i.e. games) ◦ Jusqu'à 10x faster
  35. GDG Location GDG Ratoma ADB Incremental // First: sign APK,

    create APK Signature Scheme v4 file // Then, run ADB incremental $ adb install --incremental
  36. GDG Location GDG Ratoma Changements de fonctionnement • La plupart

    de changements sont limités a targetSdk R • Essayez les changements avec les bascules de fonctionnement ◦ Command-line ◦ Nouveau panneau de Développeur Options
  37. GDG Location GDG Ratoma Toggling Behavior Changes // adb shell

    am compat (enable|disable) (CHANGE_ID|CHANGE_NAME) \ PACKAGE_NAME $ adb shell am compat disable DEFAULT_SCOPED_STORAGE \ com.android.samples.android11playground
  38. GDG Location GDG Ratoma NDK Image Decoders • All decoders

    available from native code ◦ JPEG, GIF, PNG, WebP, … • No more ◦ JNI up-calling ◦ Bundling decoder libraries ◦ Bulking APK size
  39. GDG Location GDG Ratoma /* Create a decoder. */ AImageDecoder*

    decoder = nullptr; AImageDecoder_createFromAAsset(assetDescriptor, &decoder); NDK ImageDecoder
  40. GDG Location GDG Ratoma /* Create a decoder. */ AImageDecoder*

    decoder = nullptr; AImageDecoder_createFromAAsset(assetDescriptor, &decoder); /* Set up the target format */ AImageDecoder_setAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_8888); NDK ImageDecoder
  41. GDG Location GDG Ratoma /* Create a decoder. */ AImageDecoder*

    decoder = nullptr; AImageDecoder_createFromAAsset(assetDescriptor, &decoder); /* Set up the target format */ AImageDecoder_setAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_8888); /* Create a buffer for the decoded image */ const AImageDecoderHeaderInfo* headerInfo = AImageDecoder_getHeaderInfo(decoder); int32_t height = AImageDecoderHeaderInfo_getHeight(headerInfo); size_t stride = AImageDecoder_getMinimumStride(decoder); std::vector<uint8_t> bits (height * stride); NDK ImageDecoder
  42. GDG Location GDG Ratoma NDK ImageDecoder /* Create a decoder.

    */ AImageDecoder* decoder = nullptr; AImageDecoder_createFromAAsset(assetDescriptor, &decoder); /* Set up the target format. */ AImageDecoder_setAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_8888); /* Create a buffer for the decoded image */ const AImageDecoderHeaderInfo* headerInfo = AImageDecoder_getHeaderInfo(decoder); int32_t height = AImageDecoderHeaderInfo_getHeight(headerInfo); size_t stride = AImageDecoder_getMinimumStride(decoder); std::vector<uint8_t> bits (height * stride); /* Decode! */ AImageDecoder_decodeImage(decoder, bits.data(), stride, bits.size()); github.com/android/ndk-samples
  43. GDG Location GDG Ratoma Animated HEIF • Chargez des images

    animées à partir de fichiers HEIF • AnimatedImageDrawable ◦ Comme GIFs animées ◦ Mais plus petites !
  44. GDG Location GDG Ratoma Animated HEIFs val file = File("someHeifFile")

    val source = ImageDecoder.createSource(file)
  45. GDG Location GDG Ratoma Animated HEIFs val file = File(“someHeifFile”)

    val source = ImageDecoder.createSource(file) // Perform off main thread val drawable = ImageDecoder.decodeDrawable(source); if (drawable is AnimatedImageDrawable) { drawable.start() }
  46. GDG Location GDG Ratoma NDK: OpenSL ES @Deprecated • Oboe

    for the win ! • Unbundled C++ library ◦ High-performance audio ◦ Works back to API 16 ◦ Open source github.com/google/oboe
  47. GDG Location GDG Ratoma Variable refresh rate • For apps

    with their own rendering loop ◦ e.g., games • 60 frames per second used to be a given ◦ Now some devices support 90, 120 Hz ◦ Enables more flexible backoff rates • Surface.setFrameRate() 120 fps 60
  48. GDG Location GDG Ratoma NDK Thermal API • Monitor device

    thermal status change ◦ React to thermal issues • No need for JNI up-calls
  49. GDG Location GDG Ratoma NDK Thermal API // Create a

    thermal listener void nativeThermalListener(void *appCtx, AThermalStatus status) { ... switch (status) { case ATHERMAL_STATUS_SHUTDOWN: ... case ATHERMAL_STATUS_EMERGENCY: ... case ATHERMAL_STATUS_CRITICAL: ... ... } }
  50. GDG Location GDG Ratoma NDK Thermal API // Create a

    thermal listener void nativeThermalListener(void *appCtx, AThermalStatus status) { ... switch (status) { case ATHERMAL_STATUS_SHUTDOWN: ... case ATHERMAL_STATUS_EMERGENCY: ... case ATHERMAL_STATUS_CRITICAL: ... ... } } // Register the listener AThermalManager *manager = AThermal_acquireManager(); AThermal_registerThermalStatusListener(manager, NativeThermalListener, appCtx);
  51. GDG Location GDG Ratoma NDK Thermal API // Create a

    thermal listener void nativeThermalListener(void *appCtx, AThermalStatus status) { ... switch (status) { case ATHERMAL_STATUS_SHUTDOWN: ... case ATHERMAL_STATUS_EMERGENCY: ... case ATHERMAL_STATUS_CRITICAL: ... ... } } // Register the listener AThermalManager *manager = AThermal_acquireManager(); AThermal_registerThermalStatusListener(manager, NativeThermalListener, appCtx); // ... AThermal_unregisterThermalStatusListener(manager, nativeThermalListener, appCtx); AThermal_releaseManager(manager);
  52. GDG Location GDG Ratoma NNAPI v1.3 • New operations ◦

    IF, WHILE, QUANTIZED_LSTM, HARD_SWISH • New quantization scheme GPU DSP NPU Graphics Processing Unit Digital Signal Processor Neural Processing Unit • New advanced execution control ◦ Fenced compute ◦ Memory domain input/output ◦ Quality of Service(QoS)
  53. GDG Location GDG Ratoma 5G • APIs to optimize 5G

    experience ◦ Metered network state ◦ Bandwidth estimates
  54. GDG Location GDG Ratoma 5G val manager = getSystemService(ConnectivityManager::class.java) manager.registerDefaultNetworkCallback(object:

    ConnectivityManager.NetworkCallback() { override fun onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities) { if (capabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) {...} ... if (capabilities.linkDownstreamBandwidthKbps > FAST_NETWORK) {...} } })
  55. GDG Location GDG Ratoma Biometric authenticator strength val manager: BiometricManager?

    = getSystemService(BiometricManager::class.java) val strength = BiometricManager.Authenticators.BIOMETRIC_STRONG if (manager?.canAuthenticate(strength) != BiometricManager.BIOMETRIC_SUCCESS) { // Takes user to enroll biometrics security. val intent = Intent(Settings.ACTION_BIOMETRIC_ENROLL) intent.putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, strength) startActivity(intent) } else { // Authenticate user! }
  56. GDG Location GDG Ratoma L'intégration d’autofill au clavier • Voir

    autofill contenu dans le clavier ◦ Claviers logiciels ◦ Les applis de mot de passe • Sécurité - le clavier obtient l’UI a présenter les données, mais pas les données eux-même
  57. GDG Location GDG Ratoma Autofill : Keyboards // Implement InputMethodService

    methods // Autofill request from IME override fun onCreateInlineSuggestionsRequest(uiExtras: Bundle): InlineSuggestionsRequest? { // ... }
  58. GDG Location GDG Ratoma Autofill : Keyboards // Implement InputMethodService

    methods // Autofill request from IME override fun onCreateInlineSuggestionsRequest(uiExtras: Bundle): InlineSuggestionsRequest? { // ... } // Autofill response override fun onInlineSuggestionsResponse(response: InlineSuggestionsResponse): Boolean { // ... }
  59. GDG Location GDG Ratoma Autofill : Password Apps // AutofillServices

    handle onFillRequest(), create FillResponse with InlinePresentation override fun onFillRequest(...) { }
  60. GDG Location GDG Ratoma Autofill : Password Apps // AutofillServices

    handle onFillRequest(), create FillResponse with InlinePresentation override fun onFillRequest(...) { val datasetBuilder = Dataset.Builder() val inlinePresentation = InlinePresentation(...) datasetBuilder.setValue(..., inlinePresentation, ...) }
  61. GDG Location GDG Ratoma Autofill : Password Apps // AutofillServices

    handle onFillRequest(), create FillResponse with InlinePresentation override fun onFillRequest(...) { val datasetBuilder = Dataset.Builder() val inlinePresentation = InlinePresentation(...) datasetBuilder.setValue(..., inlinePresentation, ...) responseBuilder = FillResponse.Builder() val fillResponse = responseBuilder.addDataset(datasetBuilder.build()). // ... .build() // ... }
  62. GDG Location GDG Ratoma Jetpack • 70+ bibliothèques • mises

    à jour chaque deux semaines • Neuf / récent ◦ Hilt: Dependency injection (Dagger) ◦ Paging 3.0: Tout Kotlin, avec coroutines ◦ CameraX Beta ◦ … et plus !
  63. GDG Location GDG Ratoma Jetpack Compose • Nouvelle UI Toolkit

    pour Android • Reactive, Kotlin-based • Pre-alpha, développé en plein air (open source)
  64. GDG Location GDG Ratoma Android Studio • 4.0 : Stable

    ◦ Motion Editor ◦ LayoutInspector • 4.1 : Beta ◦ Database Inspector (Room, SQLite) • 4.2 : Canary ◦ Wireless debugging with Android 11 ◦ Jetpack Compose development
  65. GDG Location GDG Ratoma Google Play • Nouvelle Play console

    : complètement repensé ◦ plus claire, plus facile à utiliser ◦ policy status section ◦ acquisition reports ◦ team management • Maintenant en Beta ◦ play.google.com/console
  66. GDG Location GDG Ratoma Plus d’informations Launch videos 11 Weeks

    of Android goo.gle/android11 d.android.com/11weeksofandroid
  67. GDG Location GDG Ratoma Plus d’informations Launch videos 11 Weeks

    of Android Android 11 Meetups goo.gle/android11 d.android.com/11weeksofandroid d.android.com/android11/meetups
  68. GDG Location GDG Ratoma Plus d’informations Launch videos 11 Weeks

    of Android Android 11 Meetups Now in Android goo.gle/android11 d.android.com/11weeksofandroid d.android.com/android11/meetups articles: medium.com/androiddevelopers/tagged/now-in-android videos: youtube.com/androiddevelopers podcast: nowinandroid.googledevelopers.libsynpro.com/