Skip to content

Instantly share code, notes, and snippets.

@ndangnguyen
Last active May 13, 2020 08:25
Show Gist options
  • Save ndangnguyen/60618ceb367d3b9a25814f3b9303f225 to your computer and use it in GitHub Desktop.
Save ndangnguyen/60618ceb367d3b9a25814f3b9303f225 to your computer and use it in GitHub Desktop.
RxPermissions3
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();
}
}
/**
* 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 &lt; 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 &lt; 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 &lt; 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();
}
}
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