Skip to content

Commit

Permalink
Check whether a permission exists or not on old versions of android...
Browse files Browse the repository at this point in the history
otherwise we might unnecessarily deny a permission that we have because
it was too new for that API version. TO-DO: add an overrideable method
that says when the permission doesn't exist that by default calls
onGranted but can be overridden to do something else.

Also, remove bug comments since code fix was verified.

Lowered visibility of onResult method since it does not need to be
public
  • Loading branch information
anthonycr committed Nov 10, 2015
1 parent 8531d2f commit 4a6d690
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 52 deletions.
125 changes: 88 additions & 37 deletions library/src/main/java/com/anthonycr/grant/PermissionsManager.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package com.anthonycr.grant;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.util.Log;

import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
Expand All @@ -26,17 +26,72 @@
public final class PermissionsManager {

private static final String TAG = PermissionsManager.class.getSimpleName();
private static final String[] EMPTY_PERMISSIONS = new String[0];
private static final PermissionsManager INSTANCE = new PermissionsManager();

private final Set<String> mPendingRequests = new HashSet<>(1);
private final Set<String> mPermissions = new HashSet<>(1);
private final List<WeakReference<PermissionsResultAction>> mPendingActions = new ArrayList<>(1);


public static PermissionsManager getInstance() {
return INSTANCE;
}

private PermissionsManager() {}
private PermissionsManager() {
initializePermissionsMap();
}

/**
* This method uses reflection to read all the permissions in the Manifest class.
* This is necessary because some permissions do not exist on older versions of Android,
* since they do not exist, they will be denied when you check whether you have permission
* which is problematic since a new permission is often added where there was no previous
* permission required. We initialize a Set of available permissions and check the set
* when checking if we have permission since we want to know when we are denied a permission
* because it doesn't exist yet.
*/
private synchronized void initializePermissionsMap() {
Field[] fields = Manifest.permission.class.getFields();
for (Field field : fields) {
String name = null;
try {
name = (String) field.get("");
Log.d(TAG, name);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
mPermissions.add(name);
}
}

/**
* This method retrieves all the permissions declared in the application's manifest.
* It returns a non null array of permisions that can be declared.
*
* @param activity the Activity necessary to check what permissions we have.
* @return a non null array of permissions that are declared in the application manifest.
*/
@NonNull
private synchronized String[] getManifestPermissions(@NonNull final Activity activity) {
PackageInfo packageInfo = null;
List<String> list = new ArrayList<>(1);
try {
Log.d(TAG, activity.getPackageName());
packageInfo = activity.getPackageManager().getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "A problem occurred when retrieving permissions", e);
}
if (packageInfo != null) {
String[] permissions = packageInfo.requestedPermissions;
if (permissions != null) {
for (String perm : permissions) {
Log.d(TAG, "Manifest contained permission: " + perm);
list.add(perm);
}
}
}
return list.toArray(new String[list.size()]);
}

/**
* This method adds the {@link PermissionsResultAction} to the current list
Expand All @@ -57,11 +112,19 @@ private synchronized void addPendingAction(@NonNull String[] permissions,
mPendingActions.add(new WeakReference<>(action));
}

private synchronized void removePendingAction(PermissionsResultAction action) {
/**
* This method removes a pending action from the list of pending actions.
* It is used for cases where the permission has already been granted, so
* you immediately wish to remove the pending action from the queue and
* execute the action.
*
* @param action the action to remove
*/
private synchronized void removePendingAction(@Nullable PermissionsResultAction action) {
for (Iterator<WeakReference<PermissionsResultAction>> iterator = mPendingActions.iterator();
iterator.hasNext(); ) {
WeakReference<PermissionsResultAction> weakRef = iterator.next();
if (weakRef.get() == action) {
if (weakRef.get() == action || weakRef.get() == null) {
iterator.remove();
}
}
Expand Down Expand Up @@ -130,30 +193,8 @@ public synchronized void requestAllManifestPermissionsIfNecessary(final @Nullabl
if (activity == null) {
return;
}
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
PackageInfo packageInfo = null;
try {
Log.d(TAG, activity.getPackageName());
packageInfo = activity.getPackageManager().getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "A problem occurred when retrieving permissions", e);
}
if (packageInfo != null) {
String[] permissions = packageInfo.requestedPermissions;
if (permissions != null) {
for (String perm : permissions) {
Log.d(TAG, "Requesting permission if necessary: " + perm);
}
} else {
permissions = EMPTY_PERMISSIONS;
}
requestPermissionsIfNecessaryForResult(activity, permissions, action);
}
}
});
String[] perms = getManifestPermissions(activity);
requestPermissionsIfNecessaryForResult(activity, perms, action);
}

/**
Expand Down Expand Up @@ -266,14 +307,20 @@ public synchronized void notifyPermissionsChange(@NonNull String[] permissions,
/**
* When request permissions on devices before Android M (Android 6.0, API Level 23)
* Do the granted or denied work directly according to the permission status
* @param activity the activity to check permissions
*
* @param activity the activity to check permissions
* @param permissions the permissions names
* @param action the callback work object, containing what we what to do after permission check
* @param action the callback work object, containing what we what to do after
* permission check
*/
private void doPermissionWorkBeforeAndroidM(@Nullable Activity activity, @NonNull String[] permissions, @Nullable PermissionsResultAction action) {
private void doPermissionWorkBeforeAndroidM(@NonNull Activity activity,
@NonNull String[] permissions,
@Nullable PermissionsResultAction action) {
for (String perm : permissions) {
if (action != null) {
if (ActivityCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.checkSelfPermission(activity, perm)
!= PackageManager.PERMISSION_GRANTED
&& mPermissions.contains(perm)) {
action.onResult(perm, PackageManager.PERMISSION_DENIED);
} else {
action.onResult(perm, PackageManager.PERMISSION_GRANTED);
Expand All @@ -286,13 +333,17 @@ private void doPermissionWorkBeforeAndroidM(@Nullable Activity activity, @NonNul
* Filter the permissions list:
* If a permission is not granted, add it to the result list
* if a permission is granted, do the granted work, do not add it to the result list
* @param activity the activity to check permissions
*
* @param activity the activity to check permissions
* @param permissions all the permissions names
* @param action the callback work object, containing what we what to do after permission check
* @param action the callback work object, containing what we what to do after
* permission check
* @return a list of permissions names that are not granted yet
*/
@NonNull
private List<String> getPermissionsListToRequest(@Nullable Activity activity, @NonNull String[] permissions, @Nullable PermissionsResultAction action) {
private List<String> getPermissionsListToRequest(@NonNull Activity activity,
@NonNull String[] permissions,
@Nullable PermissionsResultAction action) {
List<String> permList = new ArrayList<>(1);
for (String perm : permissions) {
if (ActivityCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public PermissionsResultAction() {}
* @return this method returns true if its primary action has been completed
* and it should be removed from the data structure holding a reference to it.
*/
public synchronized final boolean onResult(final @NonNull String permission, int result) {
synchronized final boolean onResult(final @NonNull String permission, int result) {
if (result == PackageManager.PERMISSION_GRANTED) {
mPermissions.remove(permission);
if (mPermissions.isEmpty()) {
Expand Down
17 changes: 3 additions & 14 deletions sample/src/main/java/com/anthonycr/sample/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public void onGranted() {

@Override
public void onDenied(String permission) {
Log.i(TAG, "onDenied: Write Storage");
Log.i(TAG, "onDenied: Write Storage: " + permission);
String message = String.format(Locale.getDefault(), getString(R.string.message_denied), permission);
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
}
Expand All @@ -141,7 +141,7 @@ public void onDenied(String permission) {
break;
case R.id.button_read_storage:
PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, new PermissionsResultAction() {
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, new PermissionsResultAction() {

@Override
public void onGranted() {
Expand All @@ -151,7 +151,7 @@ public void onGranted() {

@Override
public void onDenied(String permission) {
Log.i(TAG, "onDenied: Read Storage");
Log.i(TAG, "onDenied: Read Storage: " + permission);
String message = String.format(Locale.getDefault(), getString(R.string.message_denied), permission);
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
}
Expand Down Expand Up @@ -187,17 +187,6 @@ private static void close(@Nullable Closeable closeable) {
} catch (IOException ignored) {}
}


// I found an issue, which can be seen from the logs, here is the steps:
// 1. click read contacts button, popup the dialog, select "Allow", the Log "onGranted: Read Contacts" shown.
// 2. click the read contacts button again, do the read contacts work directly, the Log "onGranted: Read Contacts" shown.
// 3. click the read storage button, popup the dialog, select "Deny".
// Bug: Both the Log "onDenied: Read Contacts" and "onDenied: Read Storage" shown.

// If we repeat the step 2 for many times, we will get many "onDenied: Read Contacts" logs on step 3.

// Reason: if we request a permission we already have, the onRequestPermissionsResult() is not invoked
// so the PermissionsManager.getInstance().notifyPermissionsChange(permissions, grantResults); is not invoked too.
}


0 comments on commit 4a6d690

Please sign in to comment.