356 lines
21 KiB
Java
356 lines
21 KiB
Java
/* Copyright (C) 2024 Arjan Schrijver
|
|
|
|
This file is part of Gadgetbridge.
|
|
|
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as published
|
|
by the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Gadgetbridge is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
package nodomain.freeyourgadget.gadgetbridge.util;
|
|
|
|
import android.Manifest;
|
|
import android.app.Activity;
|
|
import android.app.NotificationManager;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.PackageManager;
|
|
import android.os.Build;
|
|
import android.provider.Settings;
|
|
import android.widget.Toast;
|
|
|
|
import androidx.core.app.ActivityCompat;
|
|
import androidx.core.app.NotificationManagerCompat;
|
|
import androidx.core.content.ContextCompat;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Set;
|
|
|
|
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
|
|
|
public class PermissionsUtils {
|
|
private static final Logger LOG = LoggerFactory.getLogger(PermissionsUtils.class);
|
|
|
|
private static final String CUSTOM_PERM_NOTIFICATION_LISTENER = "custom_perm_notifications_listener";
|
|
private static final String CUSTOM_PERM_NOTIFICATION_SERVICE = "custom_perm_notifications_service";
|
|
private static final String CUSTOM_PERM_DISPLAY_OVER = "custom_perm_display_over";
|
|
|
|
public static ArrayList<PermissionDetails> getRequiredPermissionsList(Context context) {
|
|
ArrayList<PermissionDetails> permissionsList = new ArrayList<>();
|
|
permissionsList.add(new PermissionDetails(CUSTOM_PERM_NOTIFICATION_LISTENER, context.getString(R.string.menuitem_notifications), "Forwarding notifications to connected gadgets"));
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
permissionsList.add(new PermissionDetails(CUSTOM_PERM_NOTIFICATION_SERVICE, "Manage Do Not Disturb", "Change DND notification policy"));
|
|
permissionsList.add(new PermissionDetails(CUSTOM_PERM_DISPLAY_OVER, "Display over other apps", "Used by Bangle.js to start apps and other functionality on your phone"));
|
|
}
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) {
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.ACCESS_BACKGROUND_LOCATION, "Background location", "Required for scanning for Bluetooth devices"));
|
|
}
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.ACCESS_FINE_LOCATION, "Fine location", "Send location to gadgets which don't have GPS"));
|
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.BLUETOOTH, "Bluetooth", "Connect to Bluetooth devices"));
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.BLUETOOTH_ADMIN, "Bluetooth admin", "Discover and pair Bluetooth devices"));
|
|
}
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.BLUETOOTH_SCAN, "Bluetooth scan", "Scan for Bluetooth devices"));
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.BLUETOOTH_CONNECT, "Bluetooth connect", "Connect to Bluetooth devices"));
|
|
}
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.POST_NOTIFICATIONS, "Post notifications", "Post ongoing notification which keeps the service running"));
|
|
}
|
|
if (BuildConfig.INTERNET_ACCESS) {
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.INTERNET, "Internet access", "Synchronization with online resources"));
|
|
}
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.MEDIA_CONTENT_CONTROL, "Media content control", "Read and control media playback"));
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.READ_CONTACTS, "Contacts", "Send contacts to gadgets"));
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.READ_CALENDAR, "Calendar", "Send calendar to gadgets"));
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.RECEIVE_SMS, "Receive SMS", "Forward SMS messages to gadgets"));
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.SEND_SMS, "Send SMS", "Send SMS (canned response) from gadgets"));
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.READ_CALL_LOG, "Read call log", "Forward call log to gadgets"));
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.READ_PHONE_STATE, "Read phone state", "Read status of ongoing calls"));
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.CALL_PHONE, "Call phone", "Initiate phone calls from gadgets"));
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.PROCESS_OUTGOING_CALLS, "Process outgoing calls", "Read the number of an outgoing call to display it on a gadget"));
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.ANSWER_PHONE_CALLS, "Answer phone calls", "Answer phone calls from gadgets"));
|
|
}
|
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.READ_EXTERNAL_STORAGE, "External storage", "Using images, ringtones, app files and more"));
|
|
}
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
permissionsList.add(new PermissionDetails(Manifest.permission.QUERY_ALL_PACKAGES, "Query all packages", "Read names and icons of all installed apps"));
|
|
}
|
|
return permissionsList;
|
|
}
|
|
|
|
public static boolean checkPermission(Context context, String permission) {
|
|
if (permission.equals(CUSTOM_PERM_NOTIFICATION_LISTENER)) {
|
|
Set<String> set = NotificationManagerCompat.getEnabledListenerPackages(context);
|
|
return set.contains(context.getPackageName());
|
|
} else if (permission.equals(CUSTOM_PERM_NOTIFICATION_SERVICE) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
return ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).isNotificationPolicyAccessGranted();
|
|
} else if (permission.equals(CUSTOM_PERM_DISPLAY_OVER) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
return Settings.canDrawOverlays(context);
|
|
} else {
|
|
return ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_DENIED;
|
|
}
|
|
}
|
|
|
|
public static boolean checkAllPermissions(Context context) {
|
|
boolean result = true;
|
|
for (PermissionDetails permission : getRequiredPermissionsList(context)) {
|
|
if (!checkPermission(context, permission.getPermission())) {
|
|
result = false;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static void requestAllPermissions(Activity activity) {
|
|
}
|
|
|
|
public static void requestPermission(Activity activity, String permission) {
|
|
if (permission.equals(CUSTOM_PERM_NOTIFICATION_LISTENER)) {
|
|
try {
|
|
activity.startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
|
|
} catch (ActivityNotFoundException e) {
|
|
GB.toast(activity, "'Notification Listener Settings' activity not found", Toast.LENGTH_LONG, GB.ERROR);
|
|
}
|
|
} else if (permission.equals(CUSTOM_PERM_NOTIFICATION_SERVICE)) {
|
|
try {
|
|
activity.startActivity(new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS));
|
|
} catch (ActivityNotFoundException e) {
|
|
GB.toast(activity, "'Notification Policy' activity not found", Toast.LENGTH_LONG, GB.ERROR);
|
|
LOG.error("'Notification Policy' activity not found");
|
|
}
|
|
} else if (permission.equals(CUSTOM_PERM_DISPLAY_OVER)) {
|
|
activity.startActivity(new Intent(android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION));
|
|
} else {
|
|
ActivityCompat.requestPermissions(activity, new String[]{permission}, 0);
|
|
}
|
|
}
|
|
|
|
public static class PermissionDetails {
|
|
private String permission;
|
|
private String title;
|
|
private String summary;
|
|
|
|
public PermissionDetails(String permission, String title, String summary) {
|
|
this.permission = permission;
|
|
this.title = title;
|
|
this.summary = summary;
|
|
}
|
|
|
|
public String getPermission() {
|
|
return permission;
|
|
}
|
|
public String getTitle() {
|
|
return title;
|
|
}
|
|
public String getSummary() {
|
|
return summary;
|
|
}
|
|
}
|
|
|
|
// @TargetApi(Build.VERSION_CODES.M)
|
|
// private List<String> getWantedPermissions() {
|
|
// List<String> wantedPermissions = new ArrayList<>();
|
|
//
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.BLUETOOTH);
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADMIN) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.BLUETOOTH_ADMIN);
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.READ_CONTACTS);
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.CALL_PHONE);
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.READ_CALL_LOG);
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.READ_PHONE_STATE);
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.PROCESS_OUTGOING_CALLS) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.PROCESS_OUTGOING_CALLS);
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.RECEIVE_SMS);
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.READ_SMS);
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.SEND_SMS);
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.READ_CALENDAR);
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.ACCESS_COARSE_LOCATION);
|
|
//
|
|
// try {
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.MEDIA_CONTENT_CONTROL) == PackageManager.PERMISSION_DENIED)
|
|
// wantedPermissions.add(Manifest.permission.MEDIA_CONTENT_CONTROL);
|
|
// } catch (Exception ignored) {
|
|
// }
|
|
//
|
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
// if (pesterWithPermissions) {
|
|
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.ANSWER_PHONE_CALLS) == PackageManager.PERMISSION_DENIED) {
|
|
// wantedPermissions.add(Manifest.permission.ANSWER_PHONE_CALLS);
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) {
|
|
// if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_DENIED) {
|
|
// wantedPermissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
|
|
// }
|
|
// if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) {
|
|
// wantedPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
|
|
// }
|
|
// }
|
|
//
|
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
// if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.QUERY_ALL_PACKAGES) == PackageManager.PERMISSION_DENIED) {
|
|
// wantedPermissions.add(Manifest.permission.QUERY_ALL_PACKAGES);
|
|
// }
|
|
// }
|
|
//
|
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
// if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_DENIED) {
|
|
// wantedPermissions.add(Manifest.permission.BLUETOOTH_SCAN);
|
|
// }
|
|
// if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_DENIED) {
|
|
// wantedPermissions.add(Manifest.permission.BLUETOOTH_CONNECT);
|
|
// }
|
|
// }
|
|
//
|
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
// if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_DENIED) {
|
|
// wantedPermissions.add(Manifest.permission.POST_NOTIFICATIONS);
|
|
// }
|
|
// }
|
|
//
|
|
// if (BuildConfig.INTERNET_ACCESS) {
|
|
// if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.INTERNET) == PackageManager.PERMISSION_DENIED) {
|
|
// wantedPermissions.add(Manifest.permission.INTERNET);
|
|
// }
|
|
// }
|
|
//
|
|
// return wantedPermissions;
|
|
// }
|
|
|
|
// @TargetApi(Build.VERSION_CODES.M)
|
|
// private void checkAndRequestPermissions() {
|
|
// List<String> wantedPermissions = getWantedPermissions();
|
|
//
|
|
// if (!wantedPermissions.isEmpty()) {
|
|
// Prefs prefs = GBApplication.getPrefs();
|
|
// // If this is not the first run, we can rely on
|
|
// // shouldShowRequestPermissionRationale(String permission)
|
|
// // and ignore permissions that shouldn't or can't be requested again
|
|
// if (prefs.getBoolean("permissions_asked", false)) {
|
|
// // Don't request permissions that we shouldn't show a prompt for
|
|
// // e.g. permissions that are "Never" granted by the user or never granted by the system
|
|
// Set<String> shouldNotAsk = new HashSet<>();
|
|
// for (String wantedPermission : wantedPermissions) {
|
|
// if (!shouldShowRequestPermissionRationale(wantedPermission)) {
|
|
// shouldNotAsk.add(wantedPermission);
|
|
// }
|
|
// }
|
|
// wantedPermissions.removeAll(shouldNotAsk);
|
|
// } else {
|
|
// // Permissions have not been asked yet, but now will be
|
|
// prefs.getPreferences().edit().putBoolean("permissions_asked", true).apply();
|
|
// }
|
|
//
|
|
// if (!wantedPermissions.isEmpty()) {
|
|
// GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR);
|
|
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
// ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0);
|
|
// } else {
|
|
// requestMultiplePermissionsLauncher.launch(wantedPermissions.toArray(new String[0]));
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // The enclosed hack in it's current state cause crash on Banglejs builds tarkgetSDK=31 on a Android 13 device.
|
|
// // HACK: On Lineage we have to do this so that the permission dialog pops up
|
|
// if (fakeStateListener == null) {
|
|
// fakeStateListener = new PhoneStateListener();
|
|
// TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
|
|
// telephonyManager.listen(fakeStateListener, PhoneStateListener.LISTEN_CALL_STATE);
|
|
// telephonyManager.listen(fakeStateListener, PhoneStateListener.LISTEN_NONE);
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
/// Called from onCreate - this puts up a dialog explaining we need backgound location permissions, and then requests permissions when 'ok' pressed
|
|
// public static class LocationPermissionsDialogFragment extends DialogFragment {
|
|
// @Override
|
|
// public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
// // Use the Builder class for convenient dialog construction
|
|
// MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity());
|
|
// Context context = getContext();
|
|
// builder.setMessage(context.getString(R.string.permission_location,
|
|
// getContext().getString(R.string.app_name),
|
|
// getContext().getString(R.string.ok)))
|
|
// .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
|
// public void onClick(DialogInterface dialog, int id) {
|
|
// Intent intent = new Intent(ACTION_REQUEST_LOCATION_PERMISSIONS);
|
|
// LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
|
// }
|
|
// });
|
|
// return builder.create();
|
|
// }
|
|
// }
|
|
|
|
// Register the permissions callback, which handles the user's response to the
|
|
// system permissions dialog. Save the return value, an instance of
|
|
// ActivityResultLauncher, as an instance variable.
|
|
// This is required here rather than where it is used because it'll cause a
|
|
// "LifecycleOwners must call register before they are STARTED" if not called from onCreate
|
|
// public ActivityResultLauncher<String[]> requestMultiplePermissionsLauncher =
|
|
// registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), isGranted -> {
|
|
// if (isGranted.containsValue(true)) {
|
|
// // Permission is granted. Continue the action or workflow in your
|
|
// // app.
|
|
// } else {
|
|
// // Explain to the user that the feature is unavailable because the
|
|
// // feature requires a permission that the user has denied. At the
|
|
// // same time, respect the user's decision. Don't link to system
|
|
// // settings in an effort to convince the user to change their
|
|
// // decision.
|
|
// GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR);
|
|
// }
|
|
// });
|
|
|
|
/// Called from onCreate - this puts up a dialog explaining we need permissions, and then requests permissions when 'ok' pressed
|
|
// public static class PermissionsDialogFragment extends DialogFragment {
|
|
// @Override
|
|
// public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
// // Use the Builder class for convenient dialog construction
|
|
// MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity());
|
|
// Context context = getContext();
|
|
// builder.setMessage(context.getString(R.string.permission_request,
|
|
// getContext().getString(R.string.app_name),
|
|
// getContext().getString(R.string.ok)))
|
|
// .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
|
// public void onClick(DialogInterface dialog, int id) {
|
|
// Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
|
|
// LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
|
// }
|
|
// });
|
|
// return builder.create();
|
|
// }
|
|
// }
|
|
}
|