mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-25 08:28:57 +00:00
Support for selective permissions
This commit is contained in:
330
src/org/thoughtcrime/securesms/permissions/Permissions.java
Normal file
330
src/org/thoughtcrime/securesms/permissions/Permissions.java
Normal file
@@ -0,0 +1,330 @@
|
||||
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 org.thoughtcrime.securesms.util.LRUCache;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Permissions {
|
||||
|
||||
private static final Map<Integer, PermissionsRequest> 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<List<String>> someGrantedListener;
|
||||
private Consumer<List<String>> someDeniedListener;
|
||||
private Consumer<List<String>> somePermanentlyDeniedListener;
|
||||
|
||||
private @DrawableRes int[] rationalDialogHeader;
|
||||
private String rationaleDialogMessage;
|
||||
|
||||
private boolean ifNecesary;
|
||||
|
||||
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 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<List<String>> someGrantedListener) {
|
||||
this.someGrantedListener = someGrantedListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PermissionsBuilder onSomeDenied(Consumer<List<String>> someDeniedListener) {
|
||||
this.someDeniedListener = someDeniedListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PermissionsBuilder onSomePermanentlyDenied(Consumer<List<String>> 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)) {
|
||||
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<grantResults.length;i++) grantResults[i] = PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
request.onResult(requestedPermissions, grantResults, new boolean[requestedPermissions.length]);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private void executePermissionsRequestWithRationale(PermissionsRequest request) {
|
||||
RationaleDialog.createFor(permissionObject.getContext(), rationaleDialogMessage, rationalDialogHeader)
|
||||
.setPositiveButton("Continue", (dialog, which) -> executePermissionsRequest(request))
|
||||
.setNegativeButton("Not now", null)
|
||||
.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 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<permissions.length;i++) {
|
||||
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
|
||||
shouldShowRationaleDialog[i] = context.shouldShouldPermissionRationale(permissions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
resultListener.onResult(permissions, grantResults, shouldShowRationaleDialog);
|
||||
}
|
||||
|
||||
private static Intent getApplicationSettingsIntent(@NonNull Context context) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
Uri uri = Uri.fromParts("package", context.getPackageName(), null);
|
||||
intent.setData(uri);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
private abstract static class PermissionObject {
|
||||
|
||||
abstract Context getContext();
|
||||
abstract boolean shouldShouldPermissionRationale(String permission);
|
||||
abstract boolean hasAll(String... permissions);
|
||||
abstract void requestPermissions(int requestCode, String... permissions);
|
||||
|
||||
int getWindowWidth() {
|
||||
WindowManager windowManager = ServiceUtil.getWindowManager(getContext());
|
||||
Display display = windowManager.getDefaultDisplay();
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
display.getMetrics(metrics);
|
||||
|
||||
return metrics.widthPixels;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ActivityPermissionObject extends PermissionObject {
|
||||
|
||||
private Activity activity;
|
||||
|
||||
ActivityPermissionObject(@NonNull Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldShouldPermissionRationale(String permission) {
|
||||
return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAll(String... permissions) {
|
||||
return Permissions.hasAll(activity, permissions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestPermissions(int requestCode, String... permissions) {
|
||||
Permissions.requestPermissions(activity, requestCode, permissions);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FragmentPermissionObject extends PermissionObject {
|
||||
|
||||
private Fragment fragment;
|
||||
|
||||
FragmentPermissionObject(@NonNull Fragment fragment) {
|
||||
this.fragment = fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return fragment.getContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldShouldPermissionRationale(String permission) {
|
||||
return fragment.shouldShowRequestPermissionRationale(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAll(String... permissions) {
|
||||
return Permissions.hasAll(fragment.getContext(), permissions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestPermissions(int requestCode, String... permissions) {
|
||||
Permissions.requestPermissions(fragment, requestCode, permissions);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SettingsDialogListener implements Runnable {
|
||||
|
||||
private final Context context;
|
||||
private final String message;
|
||||
|
||||
SettingsDialogListener(Context context, String message) {
|
||||
this.message = message;
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle("Permission required")
|
||||
.setMessage(message)
|
||||
.setPositiveButton("Continue", (dialog, which) -> context.startActivity(getApplicationSettingsIntent(context)))
|
||||
.setNegativeButton("Cancel", null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.thoughtcrime.securesms.permissions;
|
||||
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.function.Consumer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class PermissionsRequest {
|
||||
|
||||
private final Map<String, Boolean> PRE_REQUEST_MAPPING = new HashMap<>();
|
||||
|
||||
private final @Nullable Runnable allGrantedListener;
|
||||
|
||||
private final @Nullable Runnable anyDeniedListener;
|
||||
private final @Nullable Runnable anyPermanentlyDeniedListener;
|
||||
private final @Nullable Runnable anyResultListener;
|
||||
|
||||
private final @Nullable Consumer<List<String>> someGrantedListener;
|
||||
private final @Nullable Consumer<List<String>> someDeniedListener;
|
||||
private final @Nullable Consumer<List<String>> somePermanentlyDeniedListener;
|
||||
|
||||
PermissionsRequest(@Nullable Runnable allGrantedListener,
|
||||
@Nullable Runnable anyDeniedListener,
|
||||
@Nullable Runnable anyPermanentlyDeniedListener,
|
||||
@Nullable Runnable anyResultListener,
|
||||
@Nullable Consumer<List<String>> someGrantedListener,
|
||||
@Nullable Consumer<List<String>> someDeniedListener,
|
||||
@Nullable Consumer<List<String>> somePermanentlyDeniedListener)
|
||||
{
|
||||
this.allGrantedListener = allGrantedListener;
|
||||
|
||||
this.anyDeniedListener = anyDeniedListener;
|
||||
this.anyPermanentlyDeniedListener = anyPermanentlyDeniedListener;
|
||||
this.anyResultListener = anyResultListener;
|
||||
|
||||
this.someGrantedListener = someGrantedListener;
|
||||
this.someDeniedListener = someDeniedListener;
|
||||
this.somePermanentlyDeniedListener = somePermanentlyDeniedListener;
|
||||
}
|
||||
|
||||
void onResult(String[] permissions, int[] grantResults, boolean[] shouldShowRationaleDialog) {
|
||||
List<String> granted = new ArrayList<>(permissions.length);
|
||||
List<String> denied = new ArrayList<>(permissions.length);
|
||||
List<String> permanentlyDenied = new ArrayList<>(permissions.length);
|
||||
|
||||
for (int i = 0; i < permissions.length; i++) {
|
||||
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
|
||||
granted.add(permissions[i]);
|
||||
} else {
|
||||
boolean preRequestShouldShowRationaleDialog = PRE_REQUEST_MAPPING.get(permissions[i]);
|
||||
|
||||
if ((somePermanentlyDeniedListener != null || anyPermanentlyDeniedListener != null) &&
|
||||
!preRequestShouldShowRationaleDialog && !shouldShowRationaleDialog[i])
|
||||
{
|
||||
permanentlyDenied.add(permissions[i]);
|
||||
} else {
|
||||
denied.add(permissions[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allGrantedListener != null && granted.size() > 0 && (denied.size() == 0 && permanentlyDenied.size() == 0)) {
|
||||
allGrantedListener.run();
|
||||
} else if (someGrantedListener != null && granted.size() > 0) {
|
||||
someGrantedListener.accept(granted);
|
||||
}
|
||||
|
||||
if (denied.size() > 0) {
|
||||
if (anyDeniedListener != null) anyDeniedListener.run();
|
||||
if (someDeniedListener != null) someDeniedListener.accept(denied);
|
||||
}
|
||||
|
||||
if (permanentlyDenied.size() > 0) {
|
||||
if (anyPermanentlyDeniedListener != null) anyPermanentlyDeniedListener.run();
|
||||
if (somePermanentlyDeniedListener != null) somePermanentlyDeniedListener.accept(permanentlyDenied);
|
||||
}
|
||||
|
||||
if (anyResultListener != null) {
|
||||
anyResultListener.run();
|
||||
}
|
||||
}
|
||||
|
||||
void addMapping(String permission, boolean shouldShowRationaleDialog) {
|
||||
PRE_REQUEST_MAPPING.put(permission, shouldShowRationaleDialog);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.thoughtcrime.securesms.permissions;
|
||||
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class RationaleDialog {
|
||||
|
||||
public static AlertDialog.Builder createFor(@NonNull Context context, @NonNull String message, @DrawableRes int... drawables) {
|
||||
View view = LayoutInflater.from(context).inflate(R.layout.permissions_rationale_dialog, null);
|
||||
ViewGroup header = view.findViewById(R.id.header_container);
|
||||
TextView text = view.findViewById(R.id.message);
|
||||
|
||||
for (int i=0;i<drawables.length;i++) {
|
||||
ImageView imageView = new ImageView(context);
|
||||
imageView.setImageDrawable(context.getResources().getDrawable(drawables[i]));
|
||||
imageView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
|
||||
|
||||
header.addView(imageView);
|
||||
|
||||
if (i != drawables.length - 1) {
|
||||
TextView plus = new TextView(context);
|
||||
plus.setText("+");
|
||||
plus.setTextSize(TypedValue.COMPLEX_UNIT_SP, 40);
|
||||
plus.setTextColor(Color.WHITE);
|
||||
|
||||
LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.setMargins(ViewUtil.dpToPx(context, 20), 0, ViewUtil.dpToPx(context, 20), 0);
|
||||
|
||||
plus.setLayoutParams(layoutParams);
|
||||
header.addView(plus);
|
||||
}
|
||||
}
|
||||
|
||||
text.setText(message);
|
||||
|
||||
return new AlertDialog.Builder(context, R.style.RationaleDialog).setView(view);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user