Skip to content

App crash after permissions dialog closed #288

@ThinhVu

Description

@ThinhVu

Bug report

Note that the bug occur in some of my apps (and another apps still running without any problem).

  • Reproduced on:
  • [*] Android

Description

App crash after permissions dialog closed

Steps to Reproduce

Install and run app on the first time

Versions

- Callkeep: 3.1.1 (latest)
- React Native: 0.62.2
- Android: 10
- Phone model: Vsmart Live, Samsung A20, Redmi K30 Pro, ... (maybe all Android devices)

Logs

2020-09-16 18:13:29.930 30976-30976/<package_name> E/AndroidRuntime: FATAL EXCEPTION: main
    Process: <package_name>, PID: 30976
    java.lang.RuntimeException: Unable to resume activity {<package_name>.MainActivity}: java.lang.NullPointerException: Attempt to invoke interface method 'void com.facebook.react.bridge.Callback.invoke(java.lang.Object[])' on a null object reference
        at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4267)
        at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4299)
        at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2019)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7458)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:935)
     Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'void com.facebook.react.bridge.Callback.invoke(java.lang.Object[])' on a null object reference
        at com.facebook.react.modules.permissions.PermissionsModule.onRequestPermissionsResult(PermissionsModule.java:208)
        at com.facebook.react.ReactActivityDelegate$2.invoke(ReactActivityDelegate.java:171)
        at com.facebook.react.ReactActivityDelegate.onResume(ReactActivityDelegate.java:102)
        at com.facebook.react.ReactActivity.onResume(ReactActivity.java:57)
        at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1454)
        at android.app.Activity.performResume(Activity.java:7945)
        at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4257)

Root cause:

In RNCallKeep module, we have a method to check and request permissions like so:

    @ReactMethod
    public void checkPhoneAccountPermission(ReadableArray optionalPermissions, Promise promise) {
      ...
      requestPermissions(currentActivity, allPermissions, REQUEST_READ_PHONE_STATE);
      ...
   }

The requestPermissions method above is a static method of androidx.core.app.ActivityCompat class.

Base on my investigation, react-native already included a module named PermissionsModule which will be used to request permissions for android devices.

You can read source code of this class at https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/PermissionsModule.java

When permission dialog closed, onResume event life cycle will be executed. After that, PermissionsModule::onRequestPermissionsResult will be execute (for some reason -- i don't have enough time to research it).

@Override
  public boolean onRequestPermissionsResult(
      int requestCode, String[] permissions, int[] grantResults) {
    mCallbacks.get(requestCode).invoke(grantResults, getPermissionAwareActivity());
    mCallbacks.remove(requestCode);
    return mCallbacks.size() == 0;
  }

In this method, getting mCallback of request code 1337 (aka REQUEST_READ_PHONE_STATE) return null.
And invoke it thrown a Fatal exception.

Fix

Replace

requestPermissions(currentActivity, allPermissions, REQUEST_READ_PHONE_STATE);

with


WritableArray allPermissionaw = Arguments.createArray();
for (String allPermission : allPermissions) {
   allPermissionaw.pushString(allPermission);
}

getReactApplicationContext()
                .getNativeModule(PermissionsModule.class)
                .requestMultiplePermissions(allPermissionaw, new Promise() {
                @Override
                public void resolve(@Nullable Object value) {
                    WritableMap grantedPermission = (WritableMap) value;
                    int[] grantedResult = new int[allPermissions.length];
                    for (int i=0; i<allPermissions.length; ++i) {
                        String perm = allPermissions[i];
                        grantedResult[i] = grantedPermission.getString(perm).equals("granted")
                            ? PackageManager.PERMISSION_GRANTED
                            : PackageManager.PERMISSION_DENIED;
                    }
                    RNCallKeepModule.onRequestPermissionsResult(REQUEST_READ_PHONE_STATE, allPermissions, grantedResult);
                }

                @Override
                public void reject(String code, String message) {
                    hasPhoneAccountPromise.resolve(false);
                }

                @Override
                public void reject(String code, Throwable throwable) {
                    hasPhoneAccountPromise.resolve(false);
                }

                @Override
                public void reject(String code, String message, Throwable throwable) {
                    hasPhoneAccountPromise.resolve(false);
                }

                @Override
                public void reject(Throwable throwable) {
                    hasPhoneAccountPromise.resolve(false);
                }

                @Override
                public void reject(Throwable throwable, WritableMap userInfo) {
                    hasPhoneAccountPromise.resolve(false);
                }

                @Override
                public void reject(String code, @NonNull WritableMap userInfo) {
                    hasPhoneAccountPromise.resolve(false);
                }

                @Override
                public void reject(String code, Throwable throwable, WritableMap userInfo) {
                    hasPhoneAccountPromise.resolve(false);
                }

                @Override
                public void reject(String code, String message, @NonNull WritableMap userInfo) {
                    hasPhoneAccountPromise.resolve(false);
                }

                @Override
                public void reject(String code, String message, Throwable throwable, WritableMap userInfo) {
                    hasPhoneAccountPromise.resolve(false);
                }

                @Override
                public void reject(String message) {
                    hasPhoneAccountPromise.resolve(false);
                }
            });

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions