Skip to content
This repository has been archived by the owner on Oct 5, 2021. It is now read-only.

[android] "Can request only one set of permissions at a time." #53

Open
1 of 2 tasks
daadu opened this issue Nov 5, 2020 · 5 comments
Open
1 of 2 tasks

[android] "Can request only one set of permissions at a time." #53

daadu opened this issue Nov 5, 2020 · 5 comments

Comments

@daadu
Copy link

daadu commented Nov 5, 2020

馃悰 Bug Report

Calling await LocationPermissions().requestPermissions(); with both "Fine" and "Coarse" permission in AndroidManifest.xml gives the Can request only one set of permissions at a time. in the logs. Also, giving PermissionStatus.denied (even if approved) when checkPermissions called after awaiting requestPermissions.

Expected behaviour

The error/warning should not come in and correct status should be returned by checkPermissions after requestPermissions is called.

Reproduction steps

Code to reproduce:

  Future<bool> _checkLocationPermission() async {
    final checkPermission = await LocationPermissions().checkPermissionStatus();
    printIfDebug("checkPermission: $checkPermission");
    if (checkPermission == PermissionStatus.granted ||
        checkPermission == PermissionStatus.restricted) return true;
    return false;
  }

  Future<bool> _checkAndRequestLocationPermission() async {
    // return true, if already have permission
    if (await _checkLocationPermission()) return true;

    // request permission
    final _ = await LocationPermissions().requestPermissions();
    printIfDebug("requestPermission: $_");

    // check if permission was given
    final hasPermission = await _checkLocationPermission();

    // if no permission and "showShowPermissionRationale" then go to settings and return false
    if (!hasPermission &&
        await LocationPermissions().shouldShowRequestPermissionRationale()) {
      // if shouldRequest false, then open app settings and return false
      // TODO UI that shows why this permission is required
      await LocationPermissions().openAppSettings();
      return false;
    }

    return hasPermission;
  }

This is how it it is used for checking access for location before calling WifiManager.startScan api on android

  Future<List<WifiNetwork>> scanWifi() async {
    if (await _checkAndRequestLocationPermission())
      return await WiFiForIoTPlugin.loadWifiList();
    return null;
  }

Configuration

Android SDK: 28

Version: ^3.0.0

Platform:

  • 馃摫 iOS
  • 馃 Android
@daadu
Copy link
Author

daadu commented Nov 5, 2020

I am calling checkPermissions again after requestPermissions instead of "checking" the returned value of requestPermissions because it is returning Unkown status (I assume it is because the Activity is pausing-and-resuming after the permission is granted, I am using embedding v2).

@daadu
Copy link
Author

daadu commented Nov 5, 2020

Full logs:

I/flutter ( 4962): AppLifecycleState.inactive
I/flutter ( 4962): AppLifecycleState.inactive
I/flutter ( 4962): checkPermission: PermissionStatus.denied
...
W/Activity( 4962): Can request only one set of permissions at a time
...
I/flutter ( 4962): requestPermission: PermissionStatus.unknown
I/flutter ( 4962): checkPermission: PermissionStatus.denied
...
...
I/AppStateListener( 4962): onActivityStopped for activity : MainActivity isForegound : false
I/flutter ( 4962): AppLifecycleState.paused
I/flutter ( 4962): AppLifecycleState.paused
...
I/AppStateListener( 4962): onActivityStarted for activity : MainActivity isForegound : true
...
I/flutter ( 4962): AppLifecycleState.resumed
I/flutter ( 4962): AppLifecycleState.resumed

@daadu
Copy link
Author

daadu commented Nov 5, 2020

Also my guess the bug is in returning the result for requestPermissions.

@daadu
Copy link
Author

daadu commented Nov 5, 2020

The following code is working now.

uture<bool> _checkLocationPermission() async {
    final checkPermission = await LocationPermissions().checkPermissionStatus();
    printIfDebug("checkPermission: $checkPermission");
    if (checkPermission == PermissionStatus.granted ||
        checkPermission == PermissionStatus.restricted) return true;
    return false;
  }

  Future<bool> _checkAndRequestLocationPermission() async {
    // return true, if already have permission
    if (await _checkLocationPermission()) return true;

    // request permission
    PermissionStatus requestPermission = PermissionStatus.unknown;
    int count = 0;
    while (requestPermission == PermissionStatus.unknown && count < 20) {
      requestPermission = await LocationPermissions().requestPermissions();
      printIfDebug("requestPermission: $requestPermission");
      await Future.delayed(Duration(seconds: 1));
      count++;
    }

    // check if permission was given
    final hasPermission = await _checkLocationPermission();

    // if no permission and "showShowPermissionRationale" then go to settings and return false
    if (!hasPermission &&
        await LocationPermissions().shouldShowRequestPermissionRationale()) {
      // if shouldRequest false, then open app settings and return false
      // TODO UI that shows why this permission is required
      await LocationPermissions().openAppSettings();
      return false;
    }

    return hasPermission;
  }

Basically have added a while loop that keeps on requesting until it return unkown status.

The correct behaviour should be that the method returns proper status in one attempt without loop.

Could the bug be because of Acitivyt going inactive/pause when asking for permission causing detachment?

@acarlstein
Copy link

@daadu , I run into the same issue as you.

In my case, I created quickly a prototype of an activity as an experiment.

This activity would check each permission, ask the user to allow it, and if there was any permission that wasn't a allowed, then a dialog would show up. The dialog would explain the user why that permission was needed request again for that permission.

image

Note: I wouldn't be surprise that there is an error in the code below since I created in a rush
Here is my initial code:

class PermissionsActivity : AppCompatActivity() {
    companion object {
        private val TAG = PermissionsActivity::class.java.simpleName
        private const val ACCESS_NETWORK_STATE_CODE : Int = 100
        private const val CHANGE_NETWORK_STATE_CODE : Int = 101
        private const val ACCESS_INTERNET_CODE : Int = 102
        private const val ACCESS_CAMERA_CODE : Int = 103
        private const val ACCESS_COARSE_LOCATION_CODE : Int = 104
        private const val ACCESS_FINE_LOCATION_CODE : Int = 105
        private const val ACCESS_BACKGROUND_LOCATION_CODE: Int = 106
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_permissions)
        requestPermissions()
        confirmPermissions()
    }

    private fun requestPermissions(){
        if (!hasPermission(Manifest.permission.ACCESS_NETWORK_STATE)) requestAccessNetwork()
        if (!hasPermission(Manifest.permission.CHANGE_NETWORK_STATE)) requestChangeNetwork()
        if (!hasPermission(Manifest.permission.CAMERA)) requestCamera()
        if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) requestFineLocation()
        if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
            && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) requestCoarseLocation()

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            if (!hasPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) requestBackgroundLocation()
        }
    }

    private fun hasPermission(id: String): Boolean {
        return ActivityCompat.checkSelfPermission(this, id) == PackageManager.PERMISSION_GRANTED
    }

    private fun requestAccessNetwork(){
        requestPermission(
            Manifest.permission.ACCESS_NETWORK_STATE,
            ACCESS_NETWORK_STATE_CODE
        )
    }

    private fun requestPermission(permission: String, code: Int){
        ActivityCompat.requestPermissions(
            this,
            arrayOf(permission),
            code
        )
    }

    private fun requestChangeNetwork(){
        requestPermission(
            Manifest.permission.CHANGE_NETWORK_STATE,
            CHANGE_NETWORK_STATE_CODE
        )
    }

    private fun requestCamera(){
        requestPermission(
            Manifest.permission.CAMERA,
            ACCESS_CAMERA_CODE
        )
    }

    private fun requestCoarseLocation(){
        requestPermission(
            Manifest.permission.ACCESS_COARSE_LOCATION,
            ACCESS_COARSE_LOCATION_CODE
        )
    }

    private fun requestFineLocation(){
        requestPermission(
            Manifest.permission.ACCESS_FINE_LOCATION,
            ACCESS_FINE_LOCATION_CODE
        )
    }

    @RequiresApi(Build.VERSION_CODES.Q)
    private fun requestBackgroundLocation(){
        requestPermission(
            Manifest.permission.ACCESS_BACKGROUND_LOCATION,
            ACCESS_BACKGROUND_LOCATION_CODE
        )
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String?>,
        grantResults: IntArray,
    ) {
        Log.d(TAG, "onRequestPermissionsResult()")
        when (requestCode) {
            ACCESS_NETWORK_STATE_CODE ->
                if (wasPermissionGranted(grantResults)) {
                    changeTintToGreen(R.id.imageViewNetworkAccess)
                } else {
                    showDialog(
                        "Access Network State Permission",
                        "Allow access network state permission to talk to the server",
                        requestAccessNetwork()
                    )
                }
            CHANGE_NETWORK_STATE_CODE ->
                if (wasPermissionGranted(grantResults)) {
                    changeTintToGreen(R.id.imageViewNetworkAccess)
                } else {
                    showDialog(
                        "Change Network State Permission",
                        "Allow change network state permission to talk to the server",
                        requestAccessNetwork()
                    )
                }
            ACCESS_INTERNET_CODE ->
                if (wasPermissionGranted(grantResults)) {
                    changeTintToGreen(R.id.imageViewInternetAccess)
                } else {
                    showDialog(
                        "Access Internet Permission",
                        "Allow access internet permission to talk to the server",
                        requestAccessNetwork()
                    )
                }
            ACCESS_CAMERA_CODE ->
                if (wasPermissionGranted(grantResults)) {
                    changeTintToGreen(R.id.imageViewCameraAccess)
                } else {
                    showDialog(
                        "Access Camera Permission",
                        "Allow access camera permission to scan QR barcodes",
                        requestAccessNetwork()
                    )
                }
            ACCESS_FINE_LOCATION_CODE ->
                if (wasPermissionGranted(grantResults)) {
                    changeTintToGreen(R.id.imageViewBackgroundLocationAccess)
                } else {
                    showDialog(
                        "Access Fine Location Permission",
                        "Allow access to fine location permission for fine location",
                        requestAccessNetwork()
                    )
                }
            ACCESS_COARSE_LOCATION_CODE ->
                if (wasPermissionGranted(grantResults)) {
                    changeTintToGreen(R.id.imageViewCoarseLocationAccess)
                } else {
                    showDialog(
                        "Access Coarse Location Permission",
                        "Allow access to coarse location permission for general location",
                        requestAccessNetwork()
                    )
                }
            ACCESS_BACKGROUND_LOCATION_CODE ->
                if (wasPermissionGranted(grantResults)) {
                    changeTintToGreen(R.id.imageViewNetworkAccess)
                } else {
                    showDialog(
                        "Access Background Location Permission",
                        "Allow access to background location permission for fine and general location in Android 10+",
                        requestAccessNetwork()
                    )
                }
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }

    private fun wasPermissionGranted(grantResults: IntArray): Boolean {
        return grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
    }

    private fun changeTintToGreen(id: Int){
        findViewById<ImageView>(id).setColorFilter(
            ContextCompat.getColor(
                this,
                R.color.green),
            android.graphics.PorterDuff.Mode.MULTIPLY
        );
    }

    private fun showDialog(title: String, message: String, callback: Unit){
        AlertDialog.Builder(this)
            .setTitle(title)
            .setMessage(message)
            .setPositiveButton("Allow") { _, _ ->
                callback
            }
            .setNegativeButton("Cancel") { dialog, _ ->
                dialog.dismiss()
                setResult(RESULT_CANCELED, Intent())
                finish()
            }
            .create()
            .show()
    }

    private fun confirmPermissions() {
        var result = true
        result = result and hasPermission(Manifest.permission.ACCESS_NETWORK_STATE)
        result = result and hasPermission(Manifest.permission.CHANGE_NETWORK_STATE)
        result = result and hasPermission(Manifest.permission.CAMERA)
        result = result and hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
        if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
            result = result and hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            result = result and hasPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
        }
        if (result){
            setResult(RESULT_OK, Intent())
        } else {
            setResult(RESULT_CANCELED, Intent())
        }
        finish()
    }
}

Personally, it would be great that the Android API would take care of asking the user for the permissions automatically based on what is in the AndroidManifest.xml without the developer having to spend time implementing it. It would safe lots of time but I guess that will never be the case.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants