Last active
May 13, 2020 08:25
-
-
Save ndangnguyen/60618ceb367d3b9a25814f3b9303f225 to your computer and use it in GitHub Desktop.
RxPermissions3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package mi.omiseno.smartphoneorder.utils.rxPermissions3; | |
import java.util.List; | |
import io.reactivex.rxjava3.core.Observable; | |
import io.reactivex.rxjava3.functions.BiConsumer; | |
import io.reactivex.rxjava3.functions.Function; | |
import io.reactivex.rxjava3.functions.Predicate; | |
public class Permission { | |
public final String name; | |
public final boolean granted; | |
public final boolean shouldShowRequestPermissionRationale; | |
public Permission(String name, boolean granted) { | |
this(name, granted, false); | |
} | |
public Permission(String name, boolean granted, boolean shouldShowRequestPermissionRationale) { | |
this.name = name; | |
this.granted = granted; | |
this.shouldShowRequestPermissionRationale = shouldShowRequestPermissionRationale; | |
} | |
public Permission(List<Permission> permissions) { | |
name = combineName(permissions); | |
granted = combineGranted(permissions); | |
shouldShowRequestPermissionRationale = combineShouldShowRequestPermissionRationale(permissions); | |
} | |
@Override | |
@SuppressWarnings("SimplifiableIfStatement") | |
public boolean equals(final Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
final Permission that = (Permission) o; | |
if (granted != that.granted) return false; | |
if (shouldShowRequestPermissionRationale != that.shouldShowRequestPermissionRationale) | |
return false; | |
return name.equals(that.name); | |
} | |
@Override | |
public int hashCode() { | |
int result = name.hashCode(); | |
result = 31 * result + (granted ? 1 : 0); | |
result = 31 * result + (shouldShowRequestPermissionRationale ? 1 : 0); | |
return result; | |
} | |
@Override | |
public String toString() { | |
return "Permission{" + | |
"name='" + name + '\'' + | |
", granted=" + granted + | |
", shouldShowRequestPermissionRationale=" + shouldShowRequestPermissionRationale + | |
'}'; | |
} | |
private String combineName(List<Permission> permissions) { | |
return Observable.fromIterable(permissions) | |
.map(new Function<Permission, String>() { | |
@Override | |
public String apply(Permission permission) throws Exception { | |
return permission.name; | |
} | |
}).collectInto(new StringBuilder(), new BiConsumer<StringBuilder, String>() { | |
@Override | |
public void accept(StringBuilder s, String s2) throws Exception { | |
if (s.length() == 0) { | |
s.append(s2); | |
} else { | |
s.append(", ").append(s2); | |
} | |
} | |
}).blockingGet().toString(); | |
} | |
private Boolean combineGranted(List<Permission> permissions) { | |
return Observable.fromIterable(permissions) | |
.all(new Predicate<Permission>() { | |
@Override | |
public boolean test(Permission permission) throws Exception { | |
return permission.granted; | |
} | |
}).blockingGet(); | |
} | |
private Boolean combineShouldShowRequestPermissionRationale(List<Permission> permissions) { | |
return Observable.fromIterable(permissions) | |
.any(new Predicate<Permission>() { | |
@Override | |
public boolean test(Permission permission) throws Exception { | |
return permission.shouldShowRequestPermissionRationale; | |
} | |
}).blockingGet(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* <p> | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* <p> | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package mi.omiseno.smartphoneorder.utils.rxPermissions3; | |
import android.annotation.TargetApi; | |
import android.app.Activity; | |
import android.os.Build; | |
import android.text.TextUtils; | |
import androidx.annotation.VisibleForTesting; | |
import androidx.fragment.app.Fragment; | |
import androidx.fragment.app.FragmentActivity; | |
import androidx.fragment.app.FragmentManager; | |
import java.util.ArrayList; | |
import java.util.List; | |
import io.reactivex.rxjava3.annotations.NonNull; | |
import io.reactivex.rxjava3.core.Observable; | |
import io.reactivex.rxjava3.core.ObservableSource; | |
import io.reactivex.rxjava3.core.ObservableTransformer; | |
import io.reactivex.rxjava3.functions.Function; | |
import io.reactivex.rxjava3.subjects.PublishSubject; | |
public class RxPermissions { | |
static final String TAG = RxPermissions.class.getSimpleName(); | |
static final Object TRIGGER = new Object(); | |
@VisibleForTesting | |
Lazy<RxPermissionsFragment> mRxPermissionsFragment; | |
public RxPermissions(@NonNull final FragmentActivity activity) { | |
mRxPermissionsFragment = getLazySingleton(activity.getSupportFragmentManager()); | |
} | |
public RxPermissions(@NonNull final Fragment fragment) { | |
mRxPermissionsFragment = getLazySingleton(fragment.getChildFragmentManager()); | |
} | |
@NonNull | |
private Lazy<RxPermissionsFragment> getLazySingleton(@NonNull final FragmentManager fragmentManager) { | |
return new Lazy<RxPermissionsFragment>() { | |
private RxPermissionsFragment rxPermissionsFragment; | |
@Override | |
public synchronized RxPermissionsFragment get() { | |
if (rxPermissionsFragment == null) { | |
rxPermissionsFragment = getRxPermissionsFragment(fragmentManager); | |
} | |
return rxPermissionsFragment; | |
} | |
}; | |
} | |
private RxPermissionsFragment getRxPermissionsFragment(@NonNull final FragmentManager fragmentManager) { | |
RxPermissionsFragment rxPermissionsFragment = findRxPermissionsFragment(fragmentManager); | |
boolean isNewInstance = rxPermissionsFragment == null; | |
if (isNewInstance) { | |
rxPermissionsFragment = new RxPermissionsFragment(); | |
fragmentManager | |
.beginTransaction() | |
.add(rxPermissionsFragment, TAG) | |
.commitNow(); | |
} | |
return rxPermissionsFragment; | |
} | |
private RxPermissionsFragment findRxPermissionsFragment(@NonNull final FragmentManager fragmentManager) { | |
return (RxPermissionsFragment) fragmentManager.findFragmentByTag(TAG); | |
} | |
public void setLogging(boolean logging) { | |
mRxPermissionsFragment.get().setLogging(logging); | |
} | |
/** | |
* Map emitted items from the source observable into {@code true} if permissions in parameters | |
* are granted, or {@code false} if not. | |
* <p> | |
* If one or several permissions have never been requested, invoke the related framework method | |
* to ask the user if he allows the permissions. | |
*/ | |
@SuppressWarnings("WeakerAccess") | |
public <T> ObservableTransformer<T, Boolean> ensure(final String... permissions) { | |
return o -> request(o, permissions) | |
// Transform Observable<Permission> to Observable<Boolean> | |
.buffer(permissions.length) | |
.flatMap((Function<List<Permission>, ObservableSource<Boolean>>) permissions1 -> { | |
if (permissions1.isEmpty()) { | |
// Occurs during orientation change, when the subject receives onComplete. | |
// In that case we don't want to propagate that empty list to the | |
// subscriber, only the onComplete. | |
return Observable.empty(); | |
} | |
// Return true if all permissions are granted. | |
for (Permission p : permissions1) { | |
if (!p.granted) { | |
return Observable.just(false); | |
} | |
} | |
return Observable.just(true); | |
}); | |
} | |
/** | |
* Map emitted items from the source observable into {@link Permission} objects for each | |
* permission in parameters. | |
* <p> | |
* If one or several permissions have never been requested, invoke the related framework method | |
* to ask the user if he allows the permissions. | |
*/ | |
@SuppressWarnings("WeakerAccess") | |
public <T> ObservableTransformer<T, Permission> ensureEach(final String... permissions) { | |
return o -> request(o, permissions); | |
} | |
/** | |
* Map emitted items from the source observable into one combined {@link Permission} object. Only if all permissions are granted, | |
* permission also will be granted. If any permission has {@code shouldShowRationale} checked, than result also has it checked. | |
* <p> | |
* If one or several permissions have never been requested, invoke the related framework method | |
* to ask the user if he allows the permissions. | |
*/ | |
public <T> ObservableTransformer<T, Permission> ensureEachCombined(final String... permissions) { | |
return o -> request(o, permissions) | |
.buffer(permissions.length) | |
.flatMap((Function<List<Permission>, ObservableSource<Permission>>) permissions1 -> { | |
if (permissions1.isEmpty()) { | |
return Observable.empty(); | |
} | |
return Observable.just(new Permission(permissions1)); | |
}); | |
} | |
/** | |
* Request permissions immediately, <b>must be invoked during initialization phase | |
* of your application</b>. | |
*/ | |
@SuppressWarnings({"WeakerAccess", "unused"}) | |
public Observable<Boolean> request(final String... permissions) { | |
return Observable.just(TRIGGER).compose(ensure(permissions)); | |
} | |
/** | |
* Request permissions immediately, <b>must be invoked during initialization phase | |
* of your application</b>. | |
*/ | |
@SuppressWarnings({"WeakerAccess", "unused"}) | |
public Observable<Permission> requestEach(final String... permissions) { | |
return Observable.just(TRIGGER).compose(ensureEach(permissions)); | |
} | |
/** | |
* Request permissions immediately, <b>must be invoked during initialization phase | |
* of your application</b>. | |
*/ | |
public Observable<Permission> requestEachCombined(final String... permissions) { | |
return Observable.just(TRIGGER).compose(ensureEachCombined(permissions)); | |
} | |
private Observable<Permission> request(final Observable<?> trigger, final String... permissions) { | |
if (permissions == null || permissions.length == 0) { | |
throw new IllegalArgumentException("RxPermissions.request/requestEach requires at least one input permission"); | |
} | |
return oneOf(trigger, pending(permissions)) | |
.flatMap((Function<Object, Observable<Permission>>) o -> requestImplementation(permissions)); | |
} | |
private Observable<?> pending(final String... permissions) { | |
for (String p : permissions) { | |
if (!mRxPermissionsFragment.get().containsByPermission(p)) { | |
return Observable.empty(); | |
} | |
} | |
return Observable.just(TRIGGER); | |
} | |
private Observable<?> oneOf(Observable<?> trigger, Observable<?> pending) { | |
if (trigger == null) { | |
return Observable.just(TRIGGER); | |
} | |
return Observable.merge(trigger, pending); | |
} | |
@TargetApi(Build.VERSION_CODES.M) | |
private Observable<Permission> requestImplementation(final String... permissions) { | |
List<Observable<Permission>> list = new ArrayList<>(permissions.length); | |
List<String> unrequestedPermissions = new ArrayList<>(); | |
// 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); | |
if (isGranted(permission)) { | |
// Already granted, or not Android M | |
// Return a granted Permission object. | |
list.add(Observable.just(new Permission(permission, true, false))); | |
continue; | |
} | |
if (isRevoked(permission)) { | |
// Revoked by a policy, return a denied Permission object. | |
list.add(Observable.just(new Permission(permission, false, false))); | |
continue; | |
} | |
PublishSubject<Permission> subject = mRxPermissionsFragment.get().getSubjectByPermission(permission); | |
// Create a new subject if not exists | |
if (subject == null) { | |
unrequestedPermissions.add(permission); | |
subject = PublishSubject.create(); | |
mRxPermissionsFragment.get().setSubjectForPermission(permission, subject); | |
} | |
list.add(subject); | |
} | |
if (!unrequestedPermissions.isEmpty()) { | |
String[] unrequestedPermissionsArray = unrequestedPermissions.toArray(new String[unrequestedPermissions.size()]); | |
requestPermissionsFromFragment(unrequestedPermissionsArray); | |
} | |
return Observable.concat(Observable.fromIterable(list)); | |
} | |
/** | |
* Invokes Activity.shouldShowRequestPermissionRationale and wraps | |
* the returned value in an observable. | |
* <p> | |
* In case of multiple permissions, only emits true if | |
* Activity.shouldShowRequestPermissionRationale returned true for | |
* all revoked permissions. | |
* <p> | |
* You shouldn't call this method if all permissions have been granted. | |
* <p> | |
* For SDK < 23, the observable will always emit false. | |
*/ | |
@SuppressWarnings("WeakerAccess") | |
public Observable<Boolean> shouldShowRequestPermissionRationale(final Activity activity, final String... permissions) { | |
if (!isMarshmallow()) { | |
return Observable.just(false); | |
} | |
return Observable.just(shouldShowRequestPermissionRationaleImplementation(activity, permissions)); | |
} | |
@TargetApi(Build.VERSION_CODES.M) | |
private boolean shouldShowRequestPermissionRationaleImplementation(final Activity activity, final String... permissions) { | |
for (String p : permissions) { | |
if (!isGranted(p) && !activity.shouldShowRequestPermissionRationale(p)) { | |
return false; | |
} | |
} | |
return true; | |
} | |
@TargetApi(Build.VERSION_CODES.M) | |
void requestPermissionsFromFragment(String[] permissions) { | |
mRxPermissionsFragment.get().log("requestPermissionsFromFragment " + TextUtils.join(", ", permissions)); | |
mRxPermissionsFragment.get().requestPermissions(permissions); | |
} | |
/** | |
* Returns true if the permission is already granted. | |
* <p> | |
* Always true if SDK < 23. | |
*/ | |
@SuppressWarnings("WeakerAccess") | |
public boolean isGranted(String permission) { | |
return !isMarshmallow() || mRxPermissionsFragment.get().isGranted(permission); | |
} | |
/** | |
* Returns true if the permission has been revoked by a policy. | |
* <p> | |
* Always false if SDK < 23. | |
*/ | |
@SuppressWarnings("WeakerAccess") | |
public boolean isRevoked(String permission) { | |
return isMarshmallow() && mRxPermissionsFragment.get().isRevoked(permission); | |
} | |
boolean isMarshmallow() { | |
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; | |
} | |
void onRequestPermissionsResult(String[] permissions, int[] grantResults) { | |
mRxPermissionsFragment.get().onRequestPermissionsResult(permissions, grantResults, new boolean[permissions.length]); | |
} | |
@FunctionalInterface | |
public interface Lazy<V> { | |
V get(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package mi.omiseno.smartphoneorder.utils.rxPermissions3; | |
import android.annotation.TargetApi; | |
import android.content.pm.PackageManager; | |
import android.os.Build; | |
import android.os.Bundle; | |
import androidx.fragment.app.Fragment; | |
import androidx.fragment.app.FragmentActivity; | |
import java.util.HashMap; | |
import java.util.Map; | |
import io.reactivex.rxjava3.annotations.NonNull; | |
import io.reactivex.rxjava3.subjects.PublishSubject; | |
import timber.log.Timber; | |
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 boolean mLogging; | |
public RxPermissionsFragment() { | |
} | |
@Override | |
public void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setRetainInstance(true); | |
} | |
@TargetApi(Build.VERSION_CODES.M) | |
void requestPermissions(@NonNull String[] permissions) { | |
requestPermissions(permissions, PERMISSIONS_REQUEST_CODE); | |
} | |
@TargetApi(Build.VERSION_CODES.M) | |
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { | |
super.onRequestPermissionsResult(requestCode, permissions, grantResults); | |
if (requestCode != PERMISSIONS_REQUEST_CODE) return; | |
boolean[] shouldShowRequestPermissionRationale = new boolean[permissions.length]; | |
for (int i = 0; i < permissions.length; i++) { | |
shouldShowRequestPermissionRationale[i] = shouldShowRequestPermissionRationale(permissions[i]); | |
} | |
onRequestPermissionsResult(permissions, grantResults, shouldShowRequestPermissionRationale); | |
} | |
void onRequestPermissionsResult(String[] permissions, int[] grantResults, boolean[] shouldShowRequestPermissionRationale) { | |
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]); | |
if (subject == null) { | |
// No subject found | |
Timber.e("RxPermissions.onRequestPermissionsResult invoked but didn't find the corresponding permission request."); | |
return; | |
} | |
mSubjects.remove(permissions[i]); | |
boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED; | |
subject.onNext(new Permission(permissions[i], granted, shouldShowRequestPermissionRationale[i])); | |
subject.onComplete(); | |
} | |
} | |
@TargetApi(Build.VERSION_CODES.M) | |
boolean isGranted(String permission) { | |
final FragmentActivity fragmentActivity = getActivity(); | |
if (fragmentActivity == null) { | |
throw new IllegalStateException("This fragment must be attached to an activity."); | |
} | |
return fragmentActivity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; | |
} | |
@TargetApi(Build.VERSION_CODES.M) | |
boolean isRevoked(String permission) { | |
final FragmentActivity fragmentActivity = getActivity(); | |
if (fragmentActivity == null) { | |
throw new IllegalStateException("This fragment must be attached to an activity."); | |
} | |
return fragmentActivity.getPackageManager().isPermissionRevokedByPolicy(permission, getActivity().getPackageName()); | |
} | |
public void setLogging(boolean logging) { | |
mLogging = logging; | |
} | |
public PublishSubject<Permission> getSubjectByPermission(@NonNull String permission) { | |
return mSubjects.get(permission); | |
} | |
public boolean containsByPermission(@NonNull String permission) { | |
return mSubjects.containsKey(permission); | |
} | |
public void setSubjectForPermission(@NonNull String permission, @NonNull PublishSubject<Permission> subject) { | |
mSubjects.put(permission, subject); | |
} | |
void log(String message) { | |
if (mLogging) { | |
Timber.d(message); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment