Why onRequestPermissionsResult is not triggered in the Fragment after process death?

Viewed 733

I am facing a strange issue with onRequestPermissionsResult in the fragment. Basically fragment asks for camera permission inside of onCreate:

override fun onCreate(savedInstanceState: Bundle?) {
    AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
    super.onCreate(savedInstanceState)
    if(!permissionsGranted){
        requestPermissions(arrayOf(Manifest.permission.CAMERA),
                PermissionsDelegateUtil.REQUEST_PERMISSIONS_CAMERA
        )
    }
}

Then I handle permissions in the onRequestPermissionsResult:

 override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    val resultResponse = permissionsDelegateUtil.resultGranted(requestCode=requestCode, permissions = permissions, grantResults = grantResults)
    when(resultResponse){
        PermissionResult.PermissionGranted -> {
            setupCameraX()
        }
        PermissionResult.PermissionNotGrantedRetryAuto -> {
            //retrying again
            requestPermissions(arrayOf(Manifest.permission.CAMERA),
                PermissionsDelegateUtil.REQUEST_PERMISSIONS_CAMERA
        )
        }
        PermissionResult.PermissionNotGrantedCantRetry -> {
                //show a screen that user must enabled permission
        }
       PermissionResult.PermissionNotGrantedDontAsk -> {
           //Don't do anything, because you can't retry here, otherwise it will cause infinite loop.
        }
    }
}

Then permission delegate class handles permission logic:

 fun resultGranted(requestCode: Int,
                  permissions: Array<out String>,
                  grantResults: IntArray): PermissionResult {
    if (requestCode != REQUEST_PERMISSIONS_CAMERA) {
        return PermissionResult.PermissionNotGrantedDontAsk
    }
    if (grantResults.isEmpty()) {
        return PermissionResult.PermissionNotGrantedDontAsk
    }
    if(permissions[0] != Manifest.permission.CAMERA){
        return PermissionResult.PermissionNotGrantedDontAsk
    }
    return if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        PermissionResult.PermissionGranted

    } else {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (fragment.shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
                PermissionResult.PermissionNotGrantedRetryAuto
            } else{
                ///User selected don't show again checkbox.
                PermissionResult.PermissionNotGrantedCantRetry
            }
        } else{
            PermissionResult.PermissionNotGrantedRetryAuto
        }
    }
}

This code works perfectly fine until I force process death (I select don't keep activities checkbox in developer tools). After that, I return from background and the permission dialog is still present (because onCreate is triggered again and permission check is executed again).

The problem is that, after I press any button in the permission dialog, the onRequestPermissionsResult method is not being triggered in the fragment. I looked at logcat and found that permission result should be delivered, because of these logs:

2020-12-17 18:48:17.816 22496-22496/? V/GrantPermissionsActivity: Logged buttons presented and clicked permissionGroupName=android.permission-group.CAMERA uid=10135 package=com.test.app  presentedButtons=25 clickedButton=8
2020-12-17 18:48:17.821 22496-22496/? V/GrantPermissionsActivity: Permission grant result requestId=-2584100455717644829 callingUid=10135 callingPackage=com.test.app permission=android.permission.CAMERA isImplicit=false result=6

I also tried to retry calling permissions after PermissionResult.PermissionNotGrantedDontAsk is returned. It would work, but it causes an infinite loop of permission requests and response triggering and as a result, it crashes the app.

EDIT

I add a fragment without using backstack:

fun addCameraSessionFragment(supportFragmentManager: FragmentManager) {
    val fragment = supportFragmentManager.findFragmentByTag(CAMERA_SESSION_FRAGMENT_TAG)
    if (fragment == null) {
        val cameraSessionFragment =
                CameraSessionFragment()
        val fragmentTransaction = supportFragmentManager.beginTransaction()
        fragmentTransaction.replace(getTranscationRootId(), cameraSessionFragment, CAMERA_SESSION_FRAGMENT_TAG).commitNow()
    }
}

The thing is that this addition logic is triggered by ViewModel as LiveData Event. After process death occurs, addition actually occurs two times. The first is because it is the last fragment and it is restored after process death. The second is triggered by ViewModel action.

EDIT

I created a new project and could reproduce this issue again even without using fragments, just activity. Here is the repository: https://github.com/wellbranding/AndroidPermissionIssue Try to force process death, while permission dialog is open. After returning to the application, the dialog is present, but if you press any button then onRequestPermissionsResult still will not be triggered, but it should.

2 Answers

It is possible that the permissions request interaction with the user is interrupted. In this case you will receive empty permissions and results arrays which should be treated as a cancellation.

Solution: Use android:launchMode="singleInstance" for the main activity.

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.StackOverflowSolutions">
    <activity android:name="dh.sos.MainActivity" android:launchMode="singleInstance">>
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

The default mode is standard. An activity with the standard or singleTop launch mode can be instantiated multiple times.

When the default mode is used in combination with Android permission dialog + "Don't keep activities" we lose the connection between the alert and our application (Case 1).

Case 1: android:launchMode="default"

  • Enable 'Don't keep activities'
  • Run the app and ask permission
  • Put the app in the background
  • Open the app
  • Permission dialog will be shown again
  • After interaction with Android permission dialog we don't get any callback inside onRequestPermissionsResult function

Case 2: android:launchMode="singleInstance"

  • Enable 'Don't keep activities'
  • Run the app and ask permission
  • Put the app in the background
  • Open the app
  • onRequestPermissionsResult is called, but we don't see permission dialog. This case is identified as permission canceled and it's normal behaviour.

UPDATE: A new case has been found where the solution above was not enough. To be more specific: When the user returns to the app from the recent activities screen

Adding android:excludeFromRecents="true" was a partial solution, but didn't work in case when user goes to the recent screen directly from the app, and then back to the app.

Workaround: Recreate MainActivity when returning to the app where permission dialog was visible in the previous savedInstanceState.

Source code: https://github.com/dautovicharis/sos_android/tree/q_65346039

The code you provided is incomplete (even in the gist) since there is no Fragment provided. But there is nothing strange. If I understand correctly, you are requesting permissions on the activity level, not on the fragment level. So the method triggered is onRequestPermissionsResult from the activity, not the fragment. Have a look at this answer on how to implement it: https://stackoverflow.com/a/35996955/1827254

Extract:

I think you are confusing the method for fragment and activity. Had a similar issue my project last month. Please check if you have finally the following:

In AppCompatActivity use the method ActivityCompat.requestpermissions In v4 support fragment you should use requestpermissions Catch is if you call AppcompatActivity.requestpermissions in your fragment then callback will come to activity and not fragment Make sure to call super.onRequestPermissionsResult from the activity's onRequestPermissionsResult.

Related