zulip / zulip-mobile

Zulip mobile apps for Android and iOS.
https://zulip.com/apps/
Apache License 2.0
1.3k stars 655 forks source link

Target Android 14 (set targetSdkVersion to 34), by 2024-10 deadline #5877

Closed gnprice closed 1 month ago

gnprice commented 4 months ago

This is the successor to #5453 (and an annual series of previous issues linked from there). We should update our targetSdkVersion to 34, meaning Android 14.

The deadline is earlier this year than in some previous years: it's 2024-08-31. (Much like last year, we can request an extension to 2024-11-01.) is 2024-10-31, after I requested an extension.

The important steps for this upgrade are:

  1. Read about the potentially-breaking changes, and identify those that might affect us. (edit: Also the changes not tied to targetSdkVersion, because for Android 13 last year a change that should have been in the first doc wasn't, and bit us, and the latter doc had an item that would have raised our suspicions if we'd read that doc closely.)
  2. Make a WIP change to targetSdkVersion.
  3. Test the WIP change thoroughly, especially in the areas highlighted in step 1. Fix things as needed, and repeat.
chrisbobbe commented 4 months ago

From that potentially-breaking changes article:

chrisbobbe commented 4 months ago

Quick manual testing on the office Android device (Android 9) showed no problems, which I guess isn't surprising because it's not Android 14. The app opened without crashing; notifications worked, with the app foregrounded/backgrounded/closed; and image uploads worked, both from the camera and the media library. (We don't support video uploads from the media library prior to Android 13, and we don't support video uploads from the camera on Android at all.)

gnprice commented 4 months ago

showed no problems, which I guess isn't surprising because it's not Android 14.

Yeah — once targetSdkVersion is at least equal to the device's SDK version, it should be completely indifferent to the specific value. So a targetSdkVersion of 34 vs. 33 should mean exactly the same thing to any device with SDK version up through 33, i.e. up through Android 13.

gnprice commented 4 months ago

Thanks for the detailed read through the breaking-changes page! I'll do a separate read too, for a cross-check.

gnprice commented 3 months ago

OK, doing my own read-through. After I read each of these items I'm also going and reading your notes on them — thanks for the thorough validation of where some of these APIs are used!


In short:

gnprice commented 3 months ago

After our experience last year with the doc that's supposed to describe the effects of targetSdkVersion being deficient, I also read through the "Behavior changes: all apps" doc.

In short: I think there's nothing further to investigate from that doc.

gnprice commented 2 months ago

The deadline for this is now just over six weeks away (2024-10-31). The next steps are to try the upgrade and do some manual testing.

gnprice commented 2 months ago

I tested the targetSdkVersion 34 bump in a release build on my Pixel 8 running Android 14. Both of the areas above work fine: the photo picker, and notifications, in a fresh install of the app.

The photo picker working is consistent with what I found above at https://github.com/zulip/zulip-mobile/issues/5877#issuecomment-2279511937 in testing the current released version of the app. It's a bit puzzling how it's working — there's that issue https://github.com/react-native-image-picker/react-native-image-picker/issues/2299 discussed above, and it sure does look in the code like it's using the MediaStore API. But I don't see the symptoms reported in that issue; I get just one modal for picking an image, and the image I pick there successfully gets uploaded.

So that all looks good.


However. When I try to launch a debug build, it crashes. The stack trace says:

Caused by: java.lang.SecurityException: com.zulipmobile.debug:
  One of RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED should be specified
  when a receiver isn't being registered exclusively for system broadcasts
[…]
    at android.app.IActivityManager$Stub$Proxy.registerReceiverWithFeature(IActivityManager.java:5860)
    at android.app.ContextImpl.registerReceiverInternal(ContextImpl.java:1853)
    at android.app.ContextImpl.registerReceiver(ContextImpl.java:1793)
    at android.app.ContextImpl.registerReceiver(ContextImpl.java:1781)
    at android.content.ContextWrapper.registerReceiver(ContextWrapper.java:757)
    at com.facebook.react.devsupport.DevSupportManagerBase.reload(DevSupportManagerBase.java:1110)
Full stack trace ``` FATAL EXCEPTION: main Process: com.zulipmobile.debug, PID: 19362 java.lang.RuntimeException: Unable to create application com.zulipmobile.MainApplication: java.lang.RuntimeException: Requested enabled DevSupportManager, but BridgeDevSupportManager class was not found or could not be created at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7403) at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2379) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loopOnce(Looper.java:232) at android.os.Looper.loop(Looper.java:317) at android.app.ActivityThread.main(ActivityThread.java:8592) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878) Caused by: java.lang.RuntimeException: Requested enabled DevSupportManager, but BridgeDevSupportManager class was not found or could not be created at com.facebook.react.devsupport.DefaultDevSupportManagerFactory.create(DefaultDevSupportManagerFactory.java:96) at com.facebook.react.ReactInstanceManager.(ReactInstanceManager.java:260) at com.facebook.react.ReactInstanceManagerBuilder.build(ReactInstanceManagerBuilder.java:358) at com.facebook.react.ReactNativeHost.createReactInstanceManager(ReactNativeHost.java:95) at expo.modules.ReactNativeHostWrapperBase.createReactInstanceManager(ReactNativeHostWrapperBase.kt:33) at com.facebook.react.ReactNativeHost.getReactInstanceManager(ReactNativeHost.java:42) at com.zulipmobile.MainApplication.onCreate(MainApplication.java:66) at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1386) at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7398) at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2379)  at android.os.Handler.dispatchMessage(Handler.java:107)  at android.os.Looper.loopOnce(Looper.java:232)  at android.os.Looper.loop(Looper.java:317)  at android.app.ActivityThread.main(ActivityThread.java:8592)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)  Caused by: java.lang.reflect.InvocationTargetException at java.lang.reflect.Constructor.newInstance0(Native Method) at java.lang.reflect.Constructor.newInstance(Constructor.java:343) at com.facebook.react.devsupport.DefaultDevSupportManagerFactory.create(DefaultDevSupportManagerFactory.java:85) at com.facebook.react.ReactInstanceManager.(ReactInstanceManager.java:260)  at com.facebook.react.ReactInstanceManagerBuilder.build(ReactInstanceManagerBuilder.java:358)  at com.facebook.react.ReactNativeHost.createReactInstanceManager(ReactNativeHost.java:95)  at expo.modules.ReactNativeHostWrapperBase.createReactInstanceManager(ReactNativeHostWrapperBase.kt:33)  at com.facebook.react.ReactNativeHost.getReactInstanceManager(ReactNativeHost.java:42)  at com.zulipmobile.MainApplication.onCreate(MainApplication.java:66)  at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1386)  at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7398)  at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2379)  at android.os.Handler.dispatchMessage(Handler.java:107)  at android.os.Looper.loopOnce(Looper.java:232)  at android.os.Looper.loop(Looper.java:317)  at android.app.ActivityThread.main(ActivityThread.java:8592)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)  Caused by: java.lang.SecurityException: com.zulipmobile.debug: One of RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED should be specified when a receiver isn't being registered exclusively for system broadcasts at android.os.Parcel.createExceptionOrNull(Parcel.java:3183) at android.os.Parcel.createException(Parcel.java:3167) at android.os.Parcel.readException(Parcel.java:3150) at android.os.Parcel.readException(Parcel.java:3092) at android.app.IActivityManager$Stub$Proxy.registerReceiverWithFeature(IActivityManager.java:5860) at android.app.ContextImpl.registerReceiverInternal(ContextImpl.java:1853) at android.app.ContextImpl.registerReceiver(ContextImpl.java:1793) at android.app.ContextImpl.registerReceiver(ContextImpl.java:1781) at android.content.ContextWrapper.registerReceiver(ContextWrapper.java:757) at com.facebook.react.devsupport.DevSupportManagerBase.reload(DevSupportManagerBase.java:1110) at com.facebook.react.devsupport.DevSupportManagerBase.reloadSettings(DevSupportManagerBase.java:735) at com.facebook.react.devsupport.DevSupportManagerBase.setDevSupportEnabled(DevSupportManagerBase.java:612) at com.facebook.react.devsupport.DevSupportManagerBase.(DevSupportManagerBase.java:205) at com.facebook.react.devsupport.BridgeDevSupportManager.(BridgeDevSupportManager.java:77) at java.lang.reflect.Constructor.newInstance0(Native Method)  at java.lang.reflect.Constructor.newInstance(Constructor.java:343)  at com.facebook.react.devsupport.DefaultDevSupportManagerFactory.create(DefaultDevSupportManagerFactory.java:85)  at com.facebook.react.ReactInstanceManager.(ReactInstanceManager.java:260)  at com.facebook.react.ReactInstanceManagerBuilder.build(ReactInstanceManagerBuilder.java:358)  at com.facebook.react.ReactNativeHost.createReactInstanceManager(ReactNativeHost.java:95)  at expo.modules.ReactNativeHostWrapperBase.createReactInstanceManager(ReactNativeHostWrapperBase.kt:33)  at com.facebook.react.ReactNativeHost.getReactInstanceManager(ReactNativeHost.java:42)  at com.zulipmobile.MainApplication.onCreate(MainApplication.java:66)  at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1386)  at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7398)  at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2379)  at android.os.Handler.dispatchMessage(Handler.java:107)  at android.os.Looper.loopOnce(Looper.java:232)  at android.os.Looper.loop(Looper.java:317)  at android.app.ActivityThread.main(ActivityThread.java:8592)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)  Caused by: android.os.RemoteException: Remote stack trace: at com.android.server.am.ActivityManagerService.registerReceiverWithFeature(ActivityManagerService.java:14469) at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:2648) at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2822) at android.os.Binder.execTransactInternal(Binder.java:1500) at android.os.Binder.execTransact(Binder.java:1444) ```

So the key line is here:

        mApplicationContext.registerReceiver(mReloadAppBroadcastReceiver, filter);

That's exactly the "runtime-registered broadcast receivers" or "context-registered receivers" item that was mentioned in the release notes. I concluded above we didn't use it because our androidx.core was too old… but that bit of RN code is using the registerReceiver method on Context directly, from the Android SDK. (So another instance of Android docs being too glib, particularly in release notes.)

So we'll need to deal with that — it's debug-only, but it completely breaks debug builds, and we do need the ability to run debug builds so we can develop changes when necessary.

Presumably RN dealt with this upstream sometime in the last year or two. It's unlikely we'll upgrade RN to that version, because upgrading RN has always been a lot of work and for this legacy codebase that's no longer worth it. It's possible that the solution will look like running our own little fork of RN that just backports one small patch atop v0.68.7, which is (still) the latest v0.68.x and is the version we're using.

chrisbobbe commented 2 months ago

Thanks for all that investigation and writeup! As discussed in the office just now, we should be able to apply the patch using patch-package, and I expect that'll be pretty straightforward.