2

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.

Viktor Vostrikov
  • 1,322
  • 3
  • 19
  • 36
  • 1
    how is the fragment added – EpicPandaForce Dec 17 '20 at 19:55
  • Maybe memory leak, so you have multiple instances of main activity or fragment in memory. If you can provide demo project I can help with debugging. – Haris Dec 20 '20 at 22:27
  • 1
    @HarisDautović Thanks. I created a new android project, pasted this code, and could reproduce the issue. https://gist.github.com/wellbranding/a2cb6f31130ffc19d6e93a8177781ad5 – Viktor Vostrikov Dec 21 '20 at 18:36
  • @ViktorVostrikov. I did research and have some assumptions about why is this happening, but don't have any solution yet. Just in case, can you check if you have the same behavior? Here is a video: https://gofile.io/d/34Cg4o (I'm using: Android API 29, Android Studio 4.4.1) – Haris Dec 22 '20 at 00:59
  • @HarisDautović yes, I have the same behavior. By pressing the terminate button in Android studio it works correctly even by denying response. However, if I enable don't keep activities in device settings, and go to background/return after it, the issue still exists. – Viktor Vostrikov Dec 22 '20 at 07:09
  • @ViktorVostrikov I think I have good news. After adding android:launchMode="singleInstance" inside Activity tag, fixed the problem for me. After returning from background this permission case was identified as PermissionNotGrantedDontAsk and in that case user don't see any permission alert. Code: https://github.com/dautovicharis/sos_android/tree/q_65346039 – Haris Dec 22 '20 at 09:57
  • From documentation: 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. And this is exactly the case we had. – Haris Dec 22 '20 at 10:06
  • Yes, but how should I notify a user about this case? I display a new fragment, which has information on why we need permissions, and a button, which asks permission again, by calling request permissions in activity. However, I immediately receive empty results in onRequestPermissionsResult as many times as I request permissions. All this occurs after process death. – Viktor Vostrikov Dec 22 '20 at 10:19
  • You can identify this case by data you get inside onRequestPermissionsResult function. In that case, you can show the permission fragment again. The most important part is that now we have the system permission dialog canceled when the user goes to the background - foreground. With the previous implementation after user return to the app, permission dialog was active and in that case delegation between system permission alters and app has been lost somehow and we didn't get any onRequestPermissionsResult callback. – Haris Dec 22 '20 at 10:40
  • Thanks, but I can't ask again, because it creates infinite loop – Viktor Vostrikov Dec 22 '20 at 10:51

2 Answers2

4

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

Haris
  • 4,130
  • 3
  • 30
  • 47
  • 2
    Comments are not for extended discussion or debugging sessions; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/226341/discussion-on-answer-by-haris-dautovic-why-onrequestpermissionsresult-is-not-tri). Please make sure to update the answer with all relevant information. – Cody Gray - on strike Dec 23 '20 at 08:24
0

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.

Eselfar
  • 3,759
  • 3
  • 23
  • 43
  • No, I think you understood it incorrectly :) I have provided a sample with activity because it is easier to reproduce. This issue occurs both on activity and fragment. I will edit my question. – Viktor Vostrikov Dec 22 '20 at 06:57
  • @ViktorVostrikov I've just tried you gist example. I'm using a Samsung Galaxy S7 (SM-G930F) running Android 7.0 and it works fine. The activity is created as expected when I come back from the background with 'keep activity' as false, the dialog is displayed, and buttons trigger `onRequestPermissionsResult` – Eselfar Dec 22 '20 at 17:37
  • this is strange indeed. Both me and Haris Dautović could reproduce this issue. I used Pixel XL 4 emulator with API 29 and Xioami mi A3 with API 29. I did not see this issue in Android 7.0. – Viktor Vostrikov Dec 22 '20 at 19:14
  • Can you share an entire demo project, with the gradle file and everything so we could run the same code? – Eselfar Dec 22 '20 at 23:25
  • ok, this is it :) https://github.com/wellbranding/AndroidPermissionIssue/tree/master – Viktor Vostrikov Dec 23 '20 at 08:15