package org.thoughtcrime.securesms.permissions; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.provider.Settings; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.util.DisplayMetrics; import android.view.Display; import android.view.ViewGroup; import android.view.WindowManager; import com.annimon.stream.Stream; import com.annimon.stream.function.Consumer; import network.loki.messenger.R; import org.thoughtcrime.securesms.util.LRUCache; import org.thoughtcrime.securesms.util.ServiceUtil; import java.lang.ref.WeakReference; import java.security.SecureRandom; import java.util.Arrays; import java.util.List; import java.util.Map; public class Permissions { private static final Map OUTSTANDING = new LRUCache<>(2); public static PermissionsBuilder with(@NonNull Activity activity) { return new PermissionsBuilder(new ActivityPermissionObject(activity)); } public static PermissionsBuilder with(@NonNull Fragment fragment) { return new PermissionsBuilder(new FragmentPermissionObject(fragment)); } public static class PermissionsBuilder { private final PermissionObject permissionObject; private String[] requestedPermissions; private Runnable allGrantedListener; private Runnable anyDeniedListener; private Runnable anyPermanentlyDeniedListener; private Runnable anyResultListener; private Consumer> someGrantedListener; private Consumer> someDeniedListener; private Consumer> somePermanentlyDeniedListener; private @DrawableRes int[] rationalDialogHeader; private String rationaleDialogMessage; private boolean ifNecesary; private boolean condition = true; PermissionsBuilder(PermissionObject permissionObject) { this.permissionObject = permissionObject; } public PermissionsBuilder request(String... requestedPermissions) { this.requestedPermissions = requestedPermissions; return this; } public PermissionsBuilder ifNecessary() { this.ifNecesary = true; return this; } public PermissionsBuilder ifNecessary(boolean condition) { this.ifNecesary = true; this.condition = condition; return this; } public PermissionsBuilder withRationaleDialog(@NonNull String message, @NonNull @DrawableRes int... headers) { this.rationalDialogHeader = headers; this.rationaleDialogMessage = message; return this; } public PermissionsBuilder withPermanentDenialDialog(@NonNull String message) { return onAnyPermanentlyDenied(new SettingsDialogListener(permissionObject.getContext(), message)); } public PermissionsBuilder onAllGranted(Runnable allGrantedListener) { this.allGrantedListener = allGrantedListener; return this; } public PermissionsBuilder onAnyDenied(Runnable anyDeniedListener) { this.anyDeniedListener = anyDeniedListener; return this; } @SuppressWarnings("WeakerAccess") public PermissionsBuilder onAnyPermanentlyDenied(Runnable anyPermanentlyDeniedListener) { this.anyPermanentlyDeniedListener = anyPermanentlyDeniedListener; return this; } public PermissionsBuilder onAnyResult(Runnable anyResultListener) { this.anyResultListener = anyResultListener; return this; } public PermissionsBuilder onSomeGranted(Consumer> someGrantedListener) { this.someGrantedListener = someGrantedListener; return this; } public PermissionsBuilder onSomeDenied(Consumer> someDeniedListener) { this.someDeniedListener = someDeniedListener; return this; } public PermissionsBuilder onSomePermanentlyDenied(Consumer> somePermanentlyDeniedListener) { this.somePermanentlyDeniedListener = somePermanentlyDeniedListener; return this; } public void execute() { PermissionsRequest request = new PermissionsRequest(allGrantedListener, anyDeniedListener, anyPermanentlyDeniedListener, anyResultListener, someGrantedListener, someDeniedListener, somePermanentlyDeniedListener); if (ifNecesary && (permissionObject.hasAll(requestedPermissions) || !condition)) { executePreGrantedPermissionsRequest(request); } else if (rationaleDialogMessage != null && rationalDialogHeader != null) { executePermissionsRequestWithRationale(request); } else { executePermissionsRequest(request); } } private void executePreGrantedPermissionsRequest(PermissionsRequest request) { int[] grantResults = new int[requestedPermissions.length]; for (int i=0;i executePermissionsRequest(request)) .setNegativeButton(R.string.Permissions_not_now, (dialog, which) -> executeNoPermissionsRequest(request)) .show() .getWindow() .setLayout((int)(permissionObject.getWindowWidth() * .75), ViewGroup.LayoutParams.WRAP_CONTENT); } private void executePermissionsRequest(PermissionsRequest request) { int requestCode = new SecureRandom().nextInt(65434) + 100; synchronized (OUTSTANDING) { OUTSTANDING.put(requestCode, request); } for (String permission : requestedPermissions) { request.addMapping(permission, permissionObject.shouldShouldPermissionRationale(permission)); } permissionObject.requestPermissions(requestCode, requestedPermissions); } private void executeNoPermissionsRequest(PermissionsRequest request) { for (String permission : requestedPermissions) { request.addMapping(permission, true); } String[] permissions = filterNotGranted(permissionObject.getContext(), requestedPermissions); int[] grantResults = Stream.of(permissions).mapToInt(permission -> PackageManager.PERMISSION_DENIED).toArray(); boolean[] showDialog = new boolean[permissions.length]; Arrays.fill(showDialog, true); request.onResult(permissions, grantResults, showDialog); } } private static void requestPermissions(@NonNull Activity activity, int requestCode, String... permissions) { ActivityCompat.requestPermissions(activity, filterNotGranted(activity, permissions), requestCode); } private static void requestPermissions(@NonNull Fragment fragment, int requestCode, String... permissions) { fragment.requestPermissions(filterNotGranted(fragment.getContext(), permissions), requestCode); } private static String[] filterNotGranted(@NonNull Context context, String... permissions) { return Stream.of(permissions) .filter(permission -> ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) .toList() .toArray(new String[0]); } public static boolean hasAny(@NonNull Context context, String... permissions) { return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Stream.of(permissions).anyMatch(permission -> ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED); } public static boolean hasAll(@NonNull Context context, String... permissions) { return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Stream.of(permissions).allMatch(permission -> ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED); } public static void onRequestPermissionsResult(Fragment fragment, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { onRequestPermissionsResult(new FragmentPermissionObject(fragment), requestCode, permissions, grantResults); } public static void onRequestPermissionsResult(Activity activity, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { onRequestPermissionsResult(new ActivityPermissionObject(activity), requestCode, permissions, grantResults); } private static void onRequestPermissionsResult(@NonNull PermissionObject context, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { PermissionsRequest resultListener; synchronized (OUTSTANDING) { resultListener = OUTSTANDING.remove(requestCode); } if (resultListener == null) return; boolean[] shouldShowRationaleDialog = new boolean[permissions.length]; for (int i=0;i context; private final String message; SettingsDialogListener(Context context, String message) { this.message = message; this.context = new WeakReference<>(context); } @Override public void run() { Context context = this.context.get(); if (context != null) { new AlertDialog.Builder(context) .setTitle(R.string.Permissions_permission_required) .setMessage(message) .setPositiveButton(R.string.Permissions_continue, (dialog, which) -> context.startActivity(getApplicationSettingsIntent(context))) .setNegativeButton(android.R.string.cancel, null) .show(); } } } }