mirror of
https://github.com/ArcaneChat/android.git
synced 2026-07-03 14:05:24 +02:00
380 lines
13 KiB
Java
380 lines
13 KiB
Java
package org.thoughtcrime.securesms.permissions;
|
|
|
|
|
|
import android.Manifest;
|
|
import android.app.Activity;
|
|
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.util.DisplayMetrics;
|
|
import android.view.Display;
|
|
import android.view.ViewGroup;
|
|
import android.view.WindowManager;
|
|
|
|
import androidx.annotation.DrawableRes;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.appcompat.app.AlertDialog;
|
|
import androidx.core.app.ActivityCompat;
|
|
import androidx.core.content.ContextCompat;
|
|
import androidx.fragment.app.Fragment;
|
|
|
|
import org.thoughtcrime.securesms.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.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
public class Permissions {
|
|
|
|
public static String[] galleryPermissions() {
|
|
// on modern androids, the gallery picker works without permissions,
|
|
// however, the "camera roll" still requires permissions.
|
|
// to get that dialog at a UX-wise good moment, we still always ask for permission when opening gallery.
|
|
// just-always-asking this also avoids the mess with handling various paths for various apis.
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
return new String[]{Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO};
|
|
} else {
|
|
return new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};
|
|
}
|
|
}
|
|
|
|
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 @DrawableRes int[] rationalDialogHeader;
|
|
private String rationaleDialogMessage;
|
|
|
|
private boolean ifNecesary;
|
|
|
|
private boolean condition = true;
|
|
|
|
private boolean alwaysGranted = false;
|
|
|
|
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 alwaysGrantOnSdk30() {
|
|
if (Build.VERSION.SDK_INT >= 30) {
|
|
alwaysGranted = true;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public PermissionsBuilder alwaysGrantOnSdk33() {
|
|
if (Build.VERSION.SDK_INT >= 33) {
|
|
alwaysGranted = 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 void execute() {
|
|
if (alwaysGranted) {
|
|
allGrantedListener.run();
|
|
return;
|
|
}
|
|
|
|
PermissionsRequest request = new PermissionsRequest(allGrantedListener, anyDeniedListener, anyPermanentlyDeniedListener, anyResultListener);
|
|
|
|
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];
|
|
Arrays.fill(grantResults, 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(R.string.perm_continue, (dialog, which) -> executePermissionsRequest(request))
|
|
.setNegativeButton(R.string.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 = new int[permissions.length];
|
|
Arrays.fill(grantResults, PackageManager.PERMISSION_DENIED);
|
|
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) {
|
|
List<String> notGranted = new ArrayList<>();
|
|
for (String permission : permissions) {
|
|
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
|
|
notGranted.add(permission);
|
|
}
|
|
}
|
|
return notGranted.toArray(new String[0]);
|
|
}
|
|
|
|
public static boolean hasAny(@NonNull Context context, String... permissions) {
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true;
|
|
for (String permission : permissions) {
|
|
if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static boolean hasAll(@NonNull Context context, String... permissions) {
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true;
|
|
for (String permission : permissions) {
|
|
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
public 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 final 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 final 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 WeakReference<Context> 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.perm_required_title)
|
|
.setMessage(message)
|
|
.setPositiveButton(R.string.perm_continue, (dialog, which) -> context.startActivity(getApplicationSettingsIntent(context)))
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
.show();
|
|
}
|
|
}
|
|
}
|
|
}
|