diff --git a/README.md b/README.md index c1dbd08d..67b5c8f0 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,16 @@ Dexter.checkPermissions(new CompositePermissionListener(snackbarMultiplePermissi **IMPORTANT**: Remember to follow the [Google design guidelines] [2] to make your application as user-friendly as possible. +**If your application has to support configuration changes based on screen rotation remember to add a call to ``Dexter`` in your Activity ``onCreate`` method as follows:** + +```java + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.sample_activity); + Dexter.continuePendingRequestsIfPossible(permissionsListener); + } +``` + Add it to your project ---------------------- diff --git a/dexter/src/main/AndroidManifest.xml b/dexter/src/main/AndroidManifest.xml index 30baf825..5ce10cb3 100644 --- a/dexter/src/main/AndroidManifest.xml +++ b/dexter/src/main/AndroidManifest.xml @@ -22,6 +22,7 @@ diff --git a/dexter/src/main/java/com/karumi/dexter/Dexter.java b/dexter/src/main/java/com/karumi/dexter/Dexter.java index 5d45f7c4..8d7e8f58 100644 --- a/dexter/src/main/java/com/karumi/dexter/Dexter.java +++ b/dexter/src/main/java/com/karumi/dexter/Dexter.java @@ -92,10 +92,29 @@ public static boolean isRequestOngoing() { } /** - * Method called whenever the DexterActivity has been created and is ready to be used + * Requests pending permissions if there were permissions lost. This method can be used to + * recover the Dexter state during a configuration change, for example when the device is + * rotated. */ - static void onActivityCreated(Activity activity) { - instance.onActivityCreated(activity); + public static void continuePendingRequestsIfPossible(MultiplePermissionsListener listener) { + instance.continuePendingRequestsIfPossible(listener); + } + + /** + * Requests pending permission if there was a permissions lost. This method can be used to + * recover the Dexter state during a configuration change, for example when the device is + * rotated. + */ + public static void continuePendingRequestIfPossible(PermissionListener listener) { + instance.continuePendingRequestIfPossible(listener); + } + + /** + * Method called whenever the DexterActivity has been created or recreated and is ready to be + * used. + */ + static void onActivityReady(Activity activity) { + instance.onActivityReady(activity); } /** diff --git a/dexter/src/main/java/com/karumi/dexter/DexterActivity.java b/dexter/src/main/java/com/karumi/dexter/DexterActivity.java index c8288322..07bef390 100644 --- a/dexter/src/main/java/com/karumi/dexter/DexterActivity.java +++ b/dexter/src/main/java/com/karumi/dexter/DexterActivity.java @@ -17,6 +17,7 @@ package com.karumi.dexter; import android.app.Activity; +import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.view.WindowManager; @@ -27,10 +28,15 @@ public final class DexterActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Dexter.onActivityCreated(this); + Dexter.onActivityReady(this); getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); } + @Override protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + Dexter.onActivityReady(this); + } + @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { Collection grantedPermissions = new LinkedList<>(); diff --git a/dexter/src/main/java/com/karumi/dexter/DexterInstance.java b/dexter/src/main/java/com/karumi/dexter/DexterInstance.java index c957ac8d..d3b2881b 100644 --- a/dexter/src/main/java/com/karumi/dexter/DexterInstance.java +++ b/dexter/src/main/java/com/karumi/dexter/DexterInstance.java @@ -23,6 +23,7 @@ import com.karumi.dexter.listener.PermissionDeniedResponse; import com.karumi.dexter.listener.PermissionGrantedResponse; import com.karumi.dexter.listener.PermissionRequest; +import com.karumi.dexter.listener.multi.EmptyMultiplePermissionsListener; import com.karumi.dexter.listener.multi.MultiplePermissionsListener; import com.karumi.dexter.listener.single.PermissionListener; import java.util.Collection; @@ -38,6 +39,8 @@ final class DexterInstance { private static final int PERMISSIONS_REQUEST_CODE = 42; + private static final MultiplePermissionsListener EMPTY_LISTENER = + new EmptyMultiplePermissionsListener(); private final Context context; private final AndroidPermissionService androidPermissionService; @@ -45,8 +48,9 @@ final class DexterInstance { private final Collection pendingPermissions; private final MultiplePermissionsReport multiplePermissionsReport; private final AtomicBoolean isRequestingPermission; + private final AtomicBoolean rationaleAccepted; private Activity activity; - private MultiplePermissionsListener listener; + private MultiplePermissionsListener listener = EMPTY_LISTENER; DexterInstance(Context context, AndroidPermissionService androidPermissionService, IntentProvider intentProvider) { @@ -56,6 +60,7 @@ final class DexterInstance { this.pendingPermissions = new TreeSet<>(); this.multiplePermissionsReport = new MultiplePermissionsReport(); this.isRequestingPermission = new AtomicBoolean(); + this.rationaleAccepted = new AtomicBoolean(); } /** @@ -86,15 +91,37 @@ void checkPermissions(MultiplePermissionsListener listener, Collection p multiplePermissionsReport.clear(); this.listener = listener; - startTransparentActivity(); + startTransparentActivityIfNeeded(); } /** - * Method called whenever the inner activity has been created and is ready to be used + * Check if there is a permission pending to be confirmed by the user and restarts the + * request for permission process. */ - void onActivityCreated(Activity activity) { - this.activity = activity; + void continuePendingRequestIfPossible(PermissionListener listener) { + MultiplePermissionsListenerToPermissionListenerAdapter adapter = + new MultiplePermissionsListenerToPermissionListenerAdapter(listener); + continuePendingRequestsIfPossible(adapter); + } + + /** + * Check if there are some permissions pending to be confirmed by the user and restarts the + * request for permission process. + */ + void continuePendingRequestsIfPossible(MultiplePermissionsListener listener) { + boolean isShowingRationale = !pendingPermissions.isEmpty() && !rationaleAccepted.get(); + this.listener = listener; + if (isShowingRationale) { + onActivityReady(activity); + } + } + /** + * Method called whenever the inner activity has been created or restarted and is ready to be + * used. + */ + void onActivityReady(Activity activity) { + this.activity = activity; Collection deniedRequests = new LinkedList<>(); Collection grantedRequests = new LinkedList<>(); @@ -134,6 +161,7 @@ void onPermissionRequestDenied(Collection permissions) { * with the permission request process */ void onContinuePermissionRequest() { + rationaleAccepted.set(true); requestPermissionsToSystem(pendingPermissions); } @@ -142,6 +170,7 @@ void onContinuePermissionRequest() { * the permission request process */ void onCancelPermissionRequest() { + rationaleAccepted.set(false); updatePermissionsAsDenied(pendingPermissions); } @@ -162,7 +191,7 @@ void requestPermissionsToSystem(Collection permissions) { permissions.toArray(new String[permissions.size()]), PERMISSIONS_REQUEST_CODE); } - private void startTransparentActivity() { + private void startTransparentActivityIfNeeded() { Intent intent = intentProvider.get(context, DexterActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); @@ -183,7 +212,7 @@ private void handleDeniedPermissions(Collection permissions) { if (shouldShowRequestRationalePermissions.isEmpty()) { requestPermissionsToSystem(permissions); - } else { + } else if (!rationaleAccepted.get()) { PermissionRationaleToken permissionToken = new PermissionRationaleToken(this); listener.onPermissionRationaleShouldBeShown(shouldShowRequestRationalePermissions, permissionToken); @@ -216,7 +245,9 @@ private void onPermissionsChecked(Collection permissions) { if (pendingPermissions.isEmpty()) { activity.finish(); isRequestingPermission.set(false); + rationaleAccepted.set(false); listener.onPermissionsChecked(multiplePermissionsReport); + listener = EMPTY_LISTENER; } } diff --git a/dexter/src/test/java/com/karumi/dexter/DexterInstanceTest.java b/dexter/src/test/java/com/karumi/dexter/DexterInstanceTest.java index 1293324c..c1025e6f 100644 --- a/dexter/src/test/java/com/karumi/dexter/DexterInstanceTest.java +++ b/dexter/src/test/java/com/karumi/dexter/DexterInstanceTest.java @@ -35,6 +35,7 @@ import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.isA; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -104,6 +105,16 @@ public void onCheckPermissionMoreThanOnceThenThrowException() { thenPermissionIsPermanentlyDenied(ANY_PERMISSION); } + @Test public void onPermissionsPendingThenShouldNotShowPermissionRationaleTwice() { + givenPermissionIsAlreadyDenied(ANY_PERMISSION); + givenShouldShowRationaleForPermission(ANY_PERMISSION); + + whenCheckPermission(permissionListener, ANY_PERMISSION); + whenContinueWithTheCheckPermissionProcess(permissionListener); + + thenPermissionRationaleIsShown(2); + } + private void givenPermissionIsAlreadyDenied(String permission) { givenPermissionIsChecked(permission, PackageManager.PERMISSION_DENIED); } @@ -129,7 +140,11 @@ private void givenShouldShowNotRationaleForPermission(String permission) { private void whenCheckPermission(PermissionListener permissionListener, String permission) { dexter.checkPermission(permissionListener, permission); - dexter.onActivityCreated(activity); + dexter.onActivityReady(activity); + } + + private void whenContinueWithTheCheckPermissionProcess(PermissionListener permissionListener) { + dexter.continuePendingRequestIfPossible(permissionListener); } private void thenPermissionIsGranted(String permission) { @@ -153,6 +168,11 @@ private void thenShouldShowRationaleForPermission(String permission) { isA(PermissionToken.class)); } + private void thenPermissionRationaleIsShown(int times) { + verify(permissionListener, times(times)).onPermissionRationaleShouldBeShown( + isA(PermissionRequest.class), isA(PermissionToken.class)); + } + private static ArgumentMatcher getPermissionGrantedResponseMatcher( final String permission) { return new ArgumentMatcher() { diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 7d05c906..f39b4725 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -33,8 +33,6 @@ diff --git a/sample/src/main/java/com/karumi/dexter/sample/SampleActivity.java b/sample/src/main/java/com/karumi/dexter/sample/SampleActivity.java index 8abd63a9..42220ff4 100644 --- a/sample/src/main/java/com/karumi/dexter/sample/SampleActivity.java +++ b/sample/src/main/java/com/karumi/dexter/sample/SampleActivity.java @@ -59,6 +59,12 @@ public class SampleActivity extends Activity { setContentView(R.layout.sample_activity); ButterKnife.bind(this); createPermissionListeners(); + /* + * If during the rotate screen process the activity has been restarted you can call this method + * to start with the check permission process without keep in an Android Bundle the state of + * the request permission process. + */ + Dexter.continuePendingRequestsIfPossible(allPermissionsListener); } @OnClick(R.id.all_permissions_button) public void onAllPermissionsButtonClicked() {