Skip to content
This repository has been archived by the owner on Feb 4, 2024. It is now read-only.

Fix handling of interrupted/canceled permission request #266

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions lib/src/main/java/com/tbruyelle/rxpermissions2/RxPermissions.java
Original file line number Diff line number Diff line change
Expand Up @@ -223,14 +223,18 @@ private Observable<?> oneOf(Observable<?> trigger, Observable<?> pending) {
}

@TargetApi(Build.VERSION_CODES.M)
private Observable<Permission> requestImplementation(final String... permissions) {
@VisibleForTesting
final Observable<Permission> requestImplementation(final String... permissions) {
List<Observable<Permission>> list = new ArrayList<>(permissions.length);
List<String> unrequestedPermissions = new ArrayList<>();

final RxPermissionsFragment f = mRxPermissionsFragment.get();
f.prepareRequest();

// In case of multiple permissions, we create an Observable for each of them.
// At the end, the observables are combined to have a unique response.
for (String permission : permissions) {
mRxPermissionsFragment.get().log("Requesting permission " + permission);
f.log("Requesting permission " + permission);
if (isGranted(permission)) {
// Already granted, or not Android M
// Return a granted Permission object.
Expand All @@ -244,21 +248,26 @@ private Observable<Permission> requestImplementation(final String... permissions
continue;
}

PublishSubject<Permission> subject = mRxPermissionsFragment.get().getSubjectByPermission(permission);
PublishSubject<Permission> subject = f.getSubjectByPermission(permission);
// Create a new subject if not exists
if (subject == null) {
unrequestedPermissions.add(permission);
subject = PublishSubject.create();
mRxPermissionsFragment.get().setSubjectForPermission(permission, subject);
f.setSubjectForPermission(permission, subject);
}

list.add(subject);
}

f.finishRequest();

if (!unrequestedPermissions.isEmpty()) {
String[] unrequestedPermissionsArray = unrequestedPermissions.toArray(new String[unrequestedPermissions.size()]);
requestPermissionsFromFragment(unrequestedPermissionsArray);
} else {
f.cancelRequest();
}

return Observable.concat(Observable.fromIterable(list));
}

Expand Down Expand Up @@ -322,8 +331,11 @@ boolean isMarshmallow() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}

@VisibleForTesting
void onRequestPermissionsResult(String permissions[], int[] grantResults) {
mRxPermissionsFragment.get().onRequestPermissionsResult(permissions, grantResults, new boolean[permissions.length]);
final RxPermissionsFragment f = mRxPermissionsFragment.get();
final int requestCode = f.getPermissionRequestCode();
f.onRequestPermissionsResult(requestCode, permissions, grantResults, new boolean[permissions.length]);
}

@FunctionalInterface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,31 @@
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.util.SparseArray;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import io.reactivex.subjects.PublishSubject;
import io.reactivex.subjects.Subject;

public class RxPermissionsFragment extends Fragment {

private static final int PERMISSIONS_REQUEST_CODE = 42;

// Contains all the current permission requests.
// Once granted or denied, they are removed from it.
private Map<String, PublishSubject<Permission>> mSubjects = new HashMap<>();
private SparseArray<Map<String, PublishSubject<Permission>>> mSubjects = new SparseArray<>();
private boolean mLogging;

private int mRequestCode = -1;
private State mRequestState = State.READY;

public RxPermissionsFragment() {
}

Expand All @@ -32,41 +39,92 @@ public void onCreate(Bundle savedInstanceState) {
setRetainInstance(true);
}

final void prepareRequest() {
if (mRequestState != State.READY) {
throw new IllegalStateException("Another request is already being prepared or waiting to be submitted.");
}
mRequestCode++;
mRequestState = State.PREPARING;
}

final void finishRequest() {
if (mRequestState != State.PREPARING) {
throw new IllegalStateException("Request is already sealed or submitted.");
}
mRequestState = State.SEALED;
}

final void cancelRequest() {
if (mRequestState == State.READY) {
throw new IllegalStateException("There is no pending request.");
}
mRequestState = State.READY;
}

@TargetApi(Build.VERSION_CODES.M)
void requestPermissions(@NonNull String[] permissions) {
requestPermissions(permissions, PERMISSIONS_REQUEST_CODE);
if (mRequestState != State.SEALED) {
throw new IllegalStateException("Call prepareRequest() and finishRequest() before submitting a request.");
}
mRequestState = State.READY;
requestPermissions(permissions, mRequestCode);
}

@TargetApi(Build.VERSION_CODES.M)
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);

if (requestCode != PERMISSIONS_REQUEST_CODE) return;
if (permissions.length > 0) {
boolean[] shouldShowRequestPermissionRationale = new boolean[permissions.length];

boolean[] shouldShowRequestPermissionRationale = new boolean[permissions.length];
for (int i = 0; i < permissions.length; i++) {
shouldShowRequestPermissionRationale[i] = shouldShowRequestPermissionRationale(permissions[i]);
}

for (int i = 0; i < permissions.length; i++) {
shouldShowRequestPermissionRationale[i] = shouldShowRequestPermissionRationale(permissions[i]);
onRequestPermissionsResult(requestCode, permissions, grantResults, shouldShowRequestPermissionRationale);
} else {
onRequestPermissionsCanceled(requestCode);
}

onRequestPermissionsResult(permissions, grantResults, shouldShowRequestPermissionRationale);
}

void onRequestPermissionsResult(String permissions[], int[] grantResults, boolean[] shouldShowRequestPermissionRationale) {
@VisibleForTesting
void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults, boolean[] shouldShowRequestPermissionRationale) {
final Map<String, PublishSubject<Permission>> subjects = getSubjects(requestCode);
// Android will throw in requestPermissions when empty array is supplied.
assert subjects != null;

for (int i = 0, size = permissions.length; i < size; i++) {
log("onRequestPermissionsResult " + permissions[i]);
// Find the corresponding subject
PublishSubject<Permission> subject = mSubjects.get(permissions[i]);
Subject<Permission> subject = subjects.get(permissions[i]);
if (subject == null) {
// No subject found
Log.e(RxPermissions.TAG, "RxPermissions.onRequestPermissionsResult invoked but didn't find the corresponding permission request.");
return;
}
mSubjects.remove(permissions[i]);
subjects.remove(permissions[i]);
boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
subject.onNext(new Permission(permissions[i], granted, shouldShowRequestPermissionRationale[i]));
subject.onComplete();
}

mSubjects.remove(requestCode);
}

@VisibleForTesting
void onRequestPermissionsCanceled(int requestCode) {
final Map<String, PublishSubject<Permission>> subjects = getSubjectsCopy(requestCode);
if (!subjects.isEmpty()) {
mSubjects.get(requestCode).clear();
for (Map.Entry<String, PublishSubject<Permission>> entry : subjects.entrySet()) {
final String permission = entry.getKey();
final Subject<Permission> subject = entry.getValue();
subject.onNext(new Permission(permission, false, false));
subject.onComplete();
}
}
mSubjects.remove(requestCode);
}

@TargetApi(Build.VERSION_CODES.M)
Expand All @@ -91,16 +149,60 @@ public void setLogging(boolean logging) {
mLogging = logging;
}

@Nullable
public PublishSubject<Permission> getSubjectByPermission(@NonNull String permission) {
return mSubjects.get(permission);
for (int i = 0, size = mSubjects.size(); i < size; i++) {
final PublishSubject<Permission> subject = mSubjects.get(mSubjects.keyAt(i)).get(permission);
if (subject != null) return subject;
}
return null;
}

public boolean containsByPermission(@NonNull String permission) {
return mSubjects.containsKey(permission);
for (int i = 0, size = mSubjects.size(); i < size; i++) {
if (mSubjects.get(mSubjects.keyAt(i)).containsKey(permission)) {
return true;
}
}
return false;
}

public void setSubjectForPermission(@NonNull String permission, @NonNull PublishSubject<Permission> subject) {
mSubjects.put(permission, subject);
if (mRequestState != State.PREPARING) {
throw new IllegalStateException("Call prepareRequest() before making a new request.");
}
getOrCreateSubjects(mRequestCode).put(permission, subject);
}

@Nullable
private Map<String, PublishSubject<Permission>> getSubjects(int requestCode) {
return mSubjects.get(requestCode);
}

@NonNull
private Map<String, PublishSubject<Permission>> getOrCreateSubjects(int requestCode) {
if (requestCode > mRequestCode) {
throw new IllegalArgumentException("Request code is too big. Current max value is " + requestCode + ".");
}
Map<String, PublishSubject<Permission>> realSubjects = mSubjects.get(requestCode);
if (realSubjects == null) {
realSubjects = new HashMap<>();
mSubjects.put(requestCode, realSubjects);
}
return realSubjects;
}

@VisibleForTesting
@NonNull
final Map<String, PublishSubject<Permission>> getSubjectsCopy(int requestCode) {
Map<String, PublishSubject<Permission>> realSubjects = mSubjects.get(requestCode);
if (realSubjects == null) realSubjects = Collections.emptyMap();
return new LinkedHashMap<>(realSubjects);
}

@VisibleForTesting
final int getPermissionRequestCode() {
return mRequestCode;
}

void log(String message) {
Expand All @@ -109,4 +211,7 @@ void log(String message) {
}
}

enum State {
READY, PREPARING, SEALED
}
}
Loading