Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
permission_handler
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
songyanzhi
permission_handler
Commits
42f49529
Unverified
Commit
42f49529
authored
Aug 28, 2023
by
Jeroen Weener
Committed by
GitHub
Aug 28, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix 'Permanently denied' on permission dialog dismiss (#1125)
* Fix dialog dismiss bug * Fixes javadoc alignment
parent
523ef41c
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
118 additions
and
8 deletions
+118
-8
permission_handler_android/CHANGELOG.md
+5
-0
permission_handler_android/android/src/main/java/com/baseflow/permissionhandler/PermissionUtils.java
+112
-7
permission_handler_android/pubspec.yaml
+1
-1
No files found.
permission_handler_android/CHANGELOG.md
View file @
42f49529
## 10.3.4
*
Fixes a bug where the permission status would return 'permanently denied'
instead of 'denied' when the user would dismiss the permission dialog.
## 10.3.3
## 10.3.3
*
Migrates the Gradle compile arguments to the example app, so they are not enforced upon consumers of the plugin.
*
Migrates the Gradle compile arguments to the example app, so they are not enforced upon consumers of the plugin.
...
...
permission_handler_android/android/src/main/java/com/baseflow/permissionhandler/PermissionUtils.java
View file @
42f49529
...
@@ -3,6 +3,7 @@ package com.baseflow.permissionhandler;
...
@@ -3,6 +3,7 @@ package com.baseflow.permissionhandler;
import
android.Manifest
;
import
android.Manifest
;
import
android.app.Activity
;
import
android.app.Activity
;
import
android.content.Context
;
import
android.content.Context
;
import
android.content.SharedPreferences
;
import
android.content.pm.PackageInfo
;
import
android.content.pm.PackageInfo
;
import
android.content.pm.PackageManager
;
import
android.content.pm.PackageManager
;
import
android.os.Build
;
import
android.os.Build
;
...
@@ -17,6 +18,7 @@ import java.util.Arrays;
...
@@ -17,6 +18,7 @@ import java.util.Arrays;
import
java.util.List
;
import
java.util.List
;
public
class
PermissionUtils
{
public
class
PermissionUtils
{
final
static
String
SHARED_PREFERENCES_PERMISSION_WAS_DENIED_BEFORE_KEY
=
"sp_permission_handler_permission_was_denied_before"
;
@PermissionConstants
.
PermissionGroup
@PermissionConstants
.
PermissionGroup
static
int
parseManifestName
(
String
permission
)
{
static
int
parseManifestName
(
String
permission
)
{
...
@@ -227,8 +229,7 @@ public class PermissionUtils {
...
@@ -227,8 +229,7 @@ public class PermissionUtils {
case
PermissionConstants
.
PERMISSION_GROUP_ACCESS_MEDIA_LOCATION
:
case
PermissionConstants
.
PERMISSION_GROUP_ACCESS_MEDIA_LOCATION
:
// The ACCESS_MEDIA_LOCATION permission is introduced in Android Q, meaning we should
// The ACCESS_MEDIA_LOCATION permission is introduced in Android Q, meaning we should
// not handle permissions on pre Android Q devices.
// not handle permissions on pre Android Q devices.
if
(
Build
.
VERSION
.
SDK_INT
<
Build
.
VERSION_CODES
.
Q
)
if
(
Build
.
VERSION
.
SDK_INT
<
Build
.
VERSION_CODES
.
Q
)
return
null
;
return
null
;
if
(
hasPermissionInManifest
(
context
,
permissionNames
,
Manifest
.
permission
.
ACCESS_MEDIA_LOCATION
))
if
(
hasPermissionInManifest
(
context
,
permissionNames
,
Manifest
.
permission
.
ACCESS_MEDIA_LOCATION
))
permissionNames
.
add
(
Manifest
.
permission
.
ACCESS_MEDIA_LOCATION
);
permissionNames
.
add
(
Manifest
.
permission
.
ACCESS_MEDIA_LOCATION
);
...
@@ -237,8 +238,7 @@ public class PermissionUtils {
...
@@ -237,8 +238,7 @@ public class PermissionUtils {
case
PermissionConstants
.
PERMISSION_GROUP_ACTIVITY_RECOGNITION
:
case
PermissionConstants
.
PERMISSION_GROUP_ACTIVITY_RECOGNITION
:
// The ACTIVITY_RECOGNITION permission is introduced in Android Q, meaning we should
// The ACTIVITY_RECOGNITION permission is introduced in Android Q, meaning we should
// not handle permissions on pre Android Q devices.
// not handle permissions on pre Android Q devices.
if
(
Build
.
VERSION
.
SDK_INT
<
Build
.
VERSION_CODES
.
Q
)
if
(
Build
.
VERSION
.
SDK_INT
<
Build
.
VERSION_CODES
.
Q
)
return
null
;
return
null
;
if
(
hasPermissionInManifest
(
context
,
permissionNames
,
Manifest
.
permission
.
ACTIVITY_RECOGNITION
))
if
(
hasPermissionInManifest
(
context
,
permissionNames
,
Manifest
.
permission
.
ACTIVITY_RECOGNITION
))
permissionNames
.
add
(
Manifest
.
permission
.
ACTIVITY_RECOGNITION
);
permissionNames
.
add
(
Manifest
.
permission
.
ACTIVITY_RECOGNITION
);
...
@@ -385,12 +385,85 @@ public class PermissionUtils {
...
@@ -385,12 +385,85 @@ public class PermissionUtils {
return
false
;
return
false
;
}
}
/**
* Returns a {@link PermissionConstants} for a given permission.
* <p>
* When {@link PackageManager#PERMISSION_DENIED} is received, we do not know if the permission was
* denied permanently. The OS does not tell us whether the user dismissed the dialog or pressed
* 'deny'. Therefore, we need a more sophisticated (read: hacky) approach to determine whether the
* permission status is {@link PermissionConstants#PERMISSION_STATUS_DENIED} or
* {@link PermissionConstants#PERMISSION_STATUS_NEVER_ASK_AGAIN}.
* <p>
* The OS behavior has been researched experimentally and is displayed in the following diagrams:
* <p>
* State machine diagram:
* <p>
* Dismissed
* ┌┐
* ┌──┘▼─────┐ Granted ┌───────┐
* │Not asked├──────────►Granted│
* └─┬───────┘ └─▲─────┘
* │ Granted │
* │Denied ┌───────────┘
* │ │
* ┌─▼────────┴┐ ┌────────────────────────────────┐
* │Denied once├────────►Denied twice(permanently denied)│
* └──▲┌───────┘ Denied └────────────────────────────────┘
* └┘
* Dismissed
* <p>
* Scenario table listing output of
* {@link ActivityCompat#shouldShowRequestPermissionRationale(Activity, String)}:
* ┌────────────┬────────────────┬─────────┬───────────────────────────────────┬─────────────────────────┐
* │ Scenario # │ Previous state │ Action │ New state │ 'Show rationale' output │
* ├────────────┼────────────────┼─────────┼───────────────────────────────────┼─────────────────────────┤
* │ 1. │ Not asked │ Dismiss │ Not asked │ false │
* │ 2. │ Not asked │ Deny │ Denied once │ true │
* │ 3. │ Denied once │ Dismiss │ Denied once │ true │
* │ 4. │ Denied once │ Deny │ Denied twice (permanently denied) │ false │
* └────────────┴────────────────┴─────────┴───────────────────────────────────┴─────────────────────────┘
* <p>
* To distinguish between scenarios, we can use
* {@link ActivityCompat#shouldShowRequestPermissionRationale(Activity, String)}. If it returns
* true, we can safely return {@link PermissionConstants#PERMISSION_STATUS_DENIED}. To distinguish
* between scenarios 1 and 4, however, we need an extra mechanism. We opt to store a boolean
* stating whether permission has been requested before. Using a combination of checking for
* showing the permission rationale and the boolean, we can distinguish all scenarios and return
* the appropriate permission status.
* <p>
* Changing permissions via the app info screen, so outside of the application, changes the
* permission state to 'Granted' if the permission is allowed, or 'Denied once' if denied. This
* behavior should not require any additional logic.
*
* @param activity the activity for context
* @param permissionName the name of the permission
* @param grantResult the result of the permission intent
* @return {@link PermissionConstants#PERMISSION_STATUS_GRANTED},
* {@link PermissionConstants#PERMISSION_STATUS_DENIED}, or
* {@link PermissionConstants#PERMISSION_STATUS_NEVER_ASK_AGAIN}.
*/
@PermissionConstants
.
PermissionStatus
@PermissionConstants
.
PermissionStatus
static
int
toPermissionStatus
(
final
Activity
activity
,
final
String
permissionName
,
int
grantResult
)
{
static
int
toPermissionStatus
(
final
Activity
activity
,
final
String
permissionName
,
int
grantResult
)
{
if
(
grantResult
==
PackageManager
.
PERMISSION_DENIED
)
{
if
(
grantResult
==
PackageManager
.
PERMISSION_DENIED
)
{
return
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
M
&&
PermissionUtils
.
isNeverAskAgainSelected
(
activity
,
permissionName
)
if
(
Build
.
VERSION
.
SDK_INT
<
Build
.
VERSION_CODES
.
M
)
{
?
PermissionConstants
.
PERMISSION_STATUS_NEVER_ASK_AGAIN
return
PermissionConstants
.
PERMISSION_STATUS_DENIED
;
:
PermissionConstants
.
PERMISSION_STATUS_DENIED
;
}
final
boolean
wasDeniedBefore
=
PermissionUtils
.
wasPermissionDeniedBefore
(
activity
,
permissionName
);
final
boolean
shouldShowRational
=
!
PermissionUtils
.
isNeverAskAgainSelected
(
activity
,
permissionName
);
//noinspection SimplifiableConditionalExpression
final
boolean
isDenied
=
wasDeniedBefore
?
!
shouldShowRational
:
shouldShowRational
;
if
(!
wasDeniedBefore
&&
isDenied
)
{
setPermissionDenied
(
activity
,
permissionName
);
}
if
(
wasDeniedBefore
&&
isDenied
)
{
return
PermissionConstants
.
PERMISSION_STATUS_NEVER_ASK_AGAIN
;
}
return
PermissionConstants
.
PERMISSION_STATUS_DENIED
;
}
}
return
PermissionConstants
.
PERMISSION_STATUS_GRANTED
;
return
PermissionConstants
.
PERMISSION_STATUS_GRANTED
;
...
@@ -448,4 +521,36 @@ public class PermissionUtils {
...
@@ -448,4 +521,36 @@ public class PermissionUtils {
return
pm
.
getPackageInfo
(
context
.
getPackageName
(),
PackageManager
.
GET_PERMISSIONS
);
return
pm
.
getPackageInfo
(
context
.
getPackageName
(),
PackageManager
.
GET_PERMISSIONS
);
}
}
}
}
/**
* Checks if permission was denied in the past by reading
* {@link PermissionUtils#SHARED_PREFERENCES_PERMISSION_WAS_DENIED_BEFORE_KEY} from
* {@link SharedPreferences}.
* <p>
* Because the state is red from shared preferences, it is persistent across application
* sessions.
*
* @param context context needed for accessing shared preferences
* @param permissionName the name of the permission
* @return whether the permission was denied in the past
*/
private
static
boolean
wasPermissionDeniedBefore
(
final
Context
context
,
final
String
permissionName
)
{
final
SharedPreferences
sharedPreferences
=
context
.
getSharedPreferences
(
permissionName
,
Context
.
MODE_PRIVATE
);
return
sharedPreferences
.
getBoolean
(
SHARED_PREFERENCES_PERMISSION_WAS_DENIED_BEFORE_KEY
,
false
);
}
/**
* Stores a boolean in {@link SharedPreferences} indicating the provided permission has been
* denied.
* <p>
* Because the state is stored in shared preferences, it is persistent across application
* sessions.
*
* @param context context needed for accessing shared preferences.
* @param permissionName the name of the permission
*/
private
static
void
setPermissionDenied
(
final
Context
context
,
final
String
permissionName
)
{
final
SharedPreferences
sharedPreferences
=
context
.
getSharedPreferences
(
permissionName
,
Context
.
MODE_PRIVATE
);
sharedPreferences
.
edit
().
putBoolean
(
SHARED_PREFERENCES_PERMISSION_WAS_DENIED_BEFORE_KEY
,
true
).
apply
();
}
}
}
permission_handler_android/pubspec.yaml
View file @
42f49529
name
:
permission_handler_android
name
:
permission_handler_android
description
:
Permission plugin for Flutter. This plugin provides the Android API to request and check permissions.
description
:
Permission plugin for Flutter. This plugin provides the Android API to request and check permissions.
homepage
:
https://github.com/baseflow/flutter-permission-handler
homepage
:
https://github.com/baseflow/flutter-permission-handler
version
:
10.3.
3
version
:
10.3.
4
environment
:
environment
:
sdk
:
"
>=2.15.0
<4.0.0"
sdk
:
"
>=2.15.0
<4.0.0"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment