Skip to content

App crash after permissions dialog closed #288

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ThinhVu opened this issue Sep 16, 2020 · 4 comments · Fixed by #324
Closed

App crash after permissions dialog closed #288

ThinhVu opened this issue Sep 16, 2020 · 4 comments · Fixed by #324

Comments

@ThinhVu
Copy link

ThinhVu commented Sep 16, 2020

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);
                }
            });
@ElectroBuddha
Copy link

I confirm that @ThinhVu's proposed solution fixes the problem !

@nerdymind-dev
Copy link

nerdymind-dev commented Nov 23, 2020

I can also confirm @ThinhVu answer worked!

Don't forget to also import com.facebook.react.bridge.WritableArray and import com.facebook.react.modules.permissions.PermissionsModule

I created a diff patch for now to ensure this fix stays in place on my local build until it can be addressed, feel free to use the following as react-native-callkeep+3.1.4.patch so you can easily apply via patch-package 😉

diff --git a/./node_modules/react-native-callkeep/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java b/./node_modules/react-native-callkeep/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java
index ad01429..b4b2959 100644
--- a/./node_modules/react-native-callkeep/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java
+++ b/./node_modules/react-native-callkeep/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java
@@ -55,9 +55,11 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
 import com.facebook.react.bridge.ReactMethod;
 import com.facebook.react.bridge.ReadableArray;
 import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.WritableArray;
 import com.facebook.react.bridge.WritableMap;
 import com.facebook.react.HeadlessJsTaskService;
 import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
+import com.facebook.react.modules.permissions.PermissionsModule;
 
 import java.lang.reflect.Array;
 import java.util.ArrayList;
@@ -253,14 +255,84 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule {
             optionalPermsArr[i] = optionalPermissions.getString(i);
         }
 
-        String[] allPermissions = Arrays.copyOf(permissions, permissions.length + optionalPermsArr.length);
+        final String[] allPermissions = Arrays.copyOf(permissions, permissions.length + optionalPermsArr.length);
         System.arraycopy(optionalPermsArr, 0, allPermissions, permissions.length, optionalPermsArr.length);
 
         hasPhoneAccountPromise = promise;
 
         if (!this.hasPermissions()) {
-            requestPermissions(currentActivity, allPermissions, REQUEST_READ_PHONE_STATE);
-             return;
+            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);
+                    }
+            });
+            return;
         }
 
         promise.resolve(!hasPhoneAccount());

@manuquentin
Copy link
Contributor

Thanks @nerdymind-dev
Can you make a PR with that ?

sparkison pushed a commit to sparkison/react-native-callkeep that referenced this issue Nov 23, 2020
@JeroenVanSteijn
Copy link

Also currently using the PR successfully. Please merge :)

danjenkins pushed a commit to nimbleape/react-native-callkeep-1 that referenced this issue Jan 6, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants