一、简介
Android 权限请求框架,已适配 Android 13
GitHub 地址:XXPermissions
一句代码搞定权限请求,从未如此简单
二、框架亮点
- 简洁易用,采用链式调用的方式,使用只需一句代码
- 支持单个权限、多个权限、单个权限组、多个权限组请求
- 不指定权限则自动获取清单文件上的危险权限进行申请
- 如果动态申请的权限没有在清单文件中注册会抛出异常
- 支持大部分国产手机直接跳转到具体的权限设置页面
- 可设置被拒绝后继续申请,直到用户授权或者永久拒绝
- 支持请求6.0及以上的悬浮窗权限和8.0及以上的安装权限
- 本框架不依赖AppCompatSupport库,兼容Eclipse和Studio
三、使用
1. 集成
- 如果你的项目 Gradle 配置是在 7.0 以下,需要在 build.gradle 文件中加入
allprojects {
repositories {
// JitPack 远程仓库:https://jitpack.io
maven { url 'https://jitpack.io' }
}
}
- 如果你的 Gradle 配置是 7.0 及以上,则需要在 settings.gradle 文件中加入
dependencyResolutionManagement {
repositories {
// JitPack 远程仓库:https://jitpack.io
maven { url 'https://jitpack.io' }
}
}
- 在项目 app 模块下的 build.gradle 文件中加入远程依赖
android {
compileSdk 33
defaultConfig {
applicationId "com.hkt.locationdemo"
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
-------------------------------------------------------------
// 权限请求框架:https://github.com/getActivity/XXPermissions
implementation 'com.github.getActivity:XXPermissions:16.2'
// 吐司框架:https://github.com/getActivity/ToastUtils
implementation 'com.github.getActivity:ToastUtils:10.5'
- 如果项目是基于 AndroidX 包,请在项目 gradle.properties 文件中加入
# 表示将第三方库迁移到 AndroidX
android.enableJetifier = true
2. 清单文件,添加权限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.hkt.locationdemo">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.BODY_SENSORS" />
<uses-permission android:name="android.permission.BODY_SENSORS_BACKGROUND" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" tools:targetApi="s" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<application
android:name=".MyApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:requestLegacyExternalStorage="true"
android:theme="@style/Theme.LocationDemo">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 通知监听服务 -->
<service
android:name=".NotificationMonitorService"
android:exported="false"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
</application>
</manifest>
3. 代码
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
// 初始化吐司工具类
ToastUtils.init(this, new WhiteToastStyle());
}
}
**
* Created on 2022/9/15 16:03
* 通知消息监控服务
* @author Gong Youqiang
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class NotificationMonitorService extends NotificationListenerService {
/**
* 当系统收到新的通知后出发回调
*/
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
super.onNotificationPosted(sbn);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Bundle extras = sbn.getNotification().extras;
if (extras != null) {
//获取通知消息标题
String title = extras.getString(Notification.EXTRA_TITLE);
// 获取通知消息内容
Object msgText = extras.getCharSequence(Notification.EXTRA_TEXT);
ToastUtils.show("监听到新的通知消息,标题为:" + title + ",内容为:" + msgText);
}
}
}
/**
* 当系统通知被删掉后出发回调
*/
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
super.onNotificationRemoved(sbn);
}
}
/**
* Created on 2022/9/15 16:05
* 权限申请拦截器
* @author Gong Youqiang
*/
public final class PermissionInterceptor implements IPermissionInterceptor {
@Override
public void grantedPermissions(Activity activity, List<String> allPermissions, List<String> grantedPermissions,
boolean all, OnPermissionCallback callback) {
if (callback == null) {
return;
}
callback.onGranted(grantedPermissions, all);
}
@Override
public void deniedPermissions(Activity activity, List<String> allPermissions, List<String> deniedPermissions,
boolean never, OnPermissionCallback callback) {
if (callback != null) {
callback.onDenied(deniedPermissions, never);
}
if (never) {
if (deniedPermissions.size() == 1 && Permission.ACCESS_MEDIA_LOCATION.equals(deniedPermissions.get(0))) {
ToastUtils.show(R.string.common_permission_media_location_hint_fail);
return;
}
showPermissionSettingDialog(activity, allPermissions, deniedPermissions, callback);
return;
}
if (deniedPermissions.size() == 1) {
String deniedPermission = deniedPermissions.get(0);
if (Permission.ACCESS_BACKGROUND_LOCATION.equals(deniedPermission)) {
ToastUtils.show(R.string.common_permission_background_location_fail_hint);
return;
}
if (Permission.BODY_SENSORS_BACKGROUND.equals(deniedPermission)) {
ToastUtils.show(R.string.common_permission_background_sensors_fail_hint);
return;
}
}
final String message;
List<String> permissionNames = PermissionNameConvert.permissionsToNames(activity, deniedPermissions);
if (!permissionNames.isEmpty()) {
message = activity.getString(R.string.common_permission_fail_assign_hint, PermissionNameConvert.listToString(permissionNames));
} else {
message = activity.getString(R.string.common_permission_fail_hint);
}
ToastUtils.show(message);
}
/**
* 显示授权对话框
*/
private void showPermissionSettingDialog(Activity activity, List<String> allPermissions,
List<String> deniedPermissions, OnPermissionCallback callback) {
if (activity == null || activity.isFinishing() ||
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed())) {
return;
}
final String message;
List<String> permissionNames = PermissionNameConvert.permissionsToNames(activity, deniedPermissions);
if (!permissionNames.isEmpty()) {
message = activity.getString(R.string.common_permission_manual_assign_fail_hint, PermissionNameConvert.listToString(permissionNames));
} else {
message = activity.getString(R.string.common_permission_manual_fail_hint);
}
// 这里的 Dialog 只是示例,没有用 DialogFragment 来处理 Dialog 生命周期
new AlertDialog.Builder(activity)
.setTitle(R.string.common_permission_alert)
.setMessage(message)
.setPositiveButton(R.string.common_permission_goto_setting_page, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
XXPermissions.startPermissionActivity(activity,
deniedPermissions, new OnPermissionPageCallback() {
@Override
public void onGranted() {
if (callback == null) {
return;
}
callback.onGranted(allPermissions, true);
}
@Override
public void onDenied() {
showPermissionSettingDialog(activity, allPermissions,
XXPermissions.getDenied(activity, allPermissions), callback);
}
});
}
})
.show();
}
}
/**
* Created on 2022/9/15 16:07
* 权限名称转换器
* @author Gong Youqiang
*/
public final class PermissionNameConvert {
/**
* 获取权限名称
*/
public static String getPermissionString(Context context, List<String> permissions) {
return listToString(permissionsToNames(context, permissions));
}
/**
* String 列表拼接成一个字符串
*/
public static String listToString(List<String> hints) {
if (hints == null || hints.isEmpty()) {
return "";
}
StringBuilder builder = new StringBuilder();
for (String text : hints) {
if (builder.length() == 0) {
builder.append(text);
} else {
builder.append("、")
.append(text);
}
}
return builder.toString();
}
/**
* 将权限列表转换成对应名称列表
*/
@NonNull
public static List<String> permissionsToNames(Context context, List<String> permissions) {
List<String> permissionNames = new ArrayList<>();
if (context == null) {
return permissionNames;
}
if (permissions == null) {
return permissionNames;
}
for (String permission : permissions) {
switch (permission) {
case Permission.READ_EXTERNAL_STORAGE:
case Permission.WRITE_EXTERNAL_STORAGE: {
String hint = context.getString(R.string.common_permission_storage);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.READ_MEDIA_IMAGES:
case Permission.READ_MEDIA_VIDEO: {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
String hint = context.getString(R.string.common_permission_image_and_video);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
}
break;
}
case Permission.READ_MEDIA_AUDIO: {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
String hint = context.getString(R.string.common_permission_audio);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
}
break;
}
case Permission.CAMERA: {
String hint = context.getString(R.string.common_permission_camera);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.RECORD_AUDIO: {
String hint = context.getString(R.string.common_permission_microphone);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.ACCESS_FINE_LOCATION:
case Permission.ACCESS_COARSE_LOCATION:
case Permission.ACCESS_BACKGROUND_LOCATION: {
String hint;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
!permissions.contains(Permission.ACCESS_FINE_LOCATION) &&
!permissions.contains(Permission.ACCESS_COARSE_LOCATION)) {
hint = context.getString(R.string.common_permission_location_background);
} else {
hint = context.getString(R.string.common_permission_location);
}
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.BODY_SENSORS:
case Permission.BODY_SENSORS_BACKGROUND: {
String hint;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
!permissions.contains(Permission.BODY_SENSORS)) {
hint = context.getString(R.string.common_permission_sensors_background);
} else {
hint = context.getString(R.string.common_permission_sensors);
}
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.BLUETOOTH_SCAN:
case Permission.BLUETOOTH_CONNECT:
case Permission.BLUETOOTH_ADVERTISE: {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
String hint = context.getString(R.string.common_permission_wireless_devices);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
}
break;
}
case Permission.NEARBY_WIFI_DEVICES: {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
String hint = context.getString(R.string.common_permission_wireless_devices);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
}
break;
}
case Permission.READ_PHONE_STATE:
case Permission.CALL_PHONE:
case Permission.ADD_VOICEMAIL:
case Permission.USE_SIP:
case Permission.READ_PHONE_NUMBERS:
case Permission.ANSWER_PHONE_CALLS: {
String hint = context.getString(R.string.common_permission_phone);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.GET_ACCOUNTS:
case Permission.READ_CONTACTS:
case Permission.WRITE_CONTACTS: {
String hint = context.getString(R.string.common_permission_contacts);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.READ_CALENDAR:
case Permission.WRITE_CALENDAR: {
String hint = context.getString(R.string.common_permission_calendar);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.READ_CALL_LOG:
case Permission.WRITE_CALL_LOG:
case Permission.PROCESS_OUTGOING_CALLS: {
String hint = context.getString(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ?
R.string.common_permission_call_log :
R.string.common_permission_phone);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.ACTIVITY_RECOGNITION: {
String hint = context.getString(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ?
R.string.common_permission_activity_recognition_30 :
R.string.common_permission_activity_recognition_29);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.ACCESS_MEDIA_LOCATION: {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
String hint = context.getString(R.string.common_permission_media_location);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
}
break;
}
case Permission.SEND_SMS:
case Permission.RECEIVE_SMS:
case Permission.READ_SMS:
case Permission.RECEIVE_WAP_PUSH:
case Permission.RECEIVE_MMS: {
String hint = context.getString(R.string.common_permission_sms);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.MANAGE_EXTERNAL_STORAGE: {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
String hint = context.getString(R.string.common_permission_manage_storage);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
}
break;
}
case Permission.REQUEST_INSTALL_PACKAGES: {
String hint = context.getString(R.string.common_permission_install);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.SYSTEM_ALERT_WINDOW: {
String hint = context.getString(R.string.common_permission_window);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.WRITE_SETTINGS: {
String hint = context.getString(R.string.common_permission_setting);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.NOTIFICATION_SERVICE: {
String hint = context.getString(R.string.common_permission_notification);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.POST_NOTIFICATIONS: {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
String hint = context.getString(R.string.common_permission_post_notifications);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
}
break;
}
case Permission.BIND_NOTIFICATION_LISTENER_SERVICE: {
String hint = context.getString(R.string.common_permission_notification_listener);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.PACKAGE_USAGE_STATS: {
String hint = context.getString(R.string.common_permission_task);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.SCHEDULE_EXACT_ALARM: {
String hint = context.getString(R.string.common_permission_alarm);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.ACCESS_NOTIFICATION_POLICY: {
String hint = context.getString(R.string.common_permission_not_disturb);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS: {
String hint = context.getString(R.string.common_permission_ignore_battery);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.BIND_VPN_SERVICE: {
String hint = context.getString(R.string.common_permission_vpn);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
case Permission.PICTURE_IN_PICTURE: {
String hint = context.getString(R.string.common_permission_picture_in_picture);
if (!permissionNames.contains(hint)) {
permissionNames.add(hint);
}
break;
}
default:
break;
}
}
return permissionNames;
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
tools:context=".MainActivity">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/btn_main_request_single"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:text="申请单个危险权限" />
<Button
android:id="@+id/btn_main_request_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请多个危险权限" />
<Button
android:id="@+id/btn_main_request_location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请定位权限" />
<Button
android:id="@+id/btn_main_request_sensors"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请传感器权限" />
<Button
android:id="@+id/btn_main_request_activity_recognition"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请身体活动权限" />
<Button
android:id="@+id/btn_main_request_bluetooth"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请蓝牙权限" />
<Button
android:id="@+id/btn_main_request_wifi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请 WIFI 权限" />
<Button
android:id="@+id/btn_main_request_media_location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请读取图片位置权限" />
<Button
android:id="@+id/btn_main_request_media_storage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请媒体文件读取权限" />
<Button
android:id="@+id/btn_main_request_manage_storage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请所有文件管理权限" />
<Button
android:id="@+id/btn_main_request_install"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请安装包权限" />
<Button
android:id="@+id/btn_main_request_window"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请悬浮窗权限" />
<Button
android:id="@+id/btn_main_request_setting"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请系统设置权限" />
<Button
android:id="@+id/btn_main_request_notification"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请通知权限" />
<Button
android:id="@+id/btn_main_request_post_notification"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请新版通知权限" />
<Button
android:id="@+id/btn_main_request_notification_listener"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请通知栏监听权限" />
<Button
android:id="@+id/btn_main_request_package"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请使用统计权限" />
<Button
android:id="@+id/btn_main_request_alarm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请闹钟提醒权限" />
<Button
android:id="@+id/btn_main_request_not_disturb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请勿扰权限" />
<Button
android:id="@+id/btn_main_request_ignore_battery"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请忽略电池优化权限" />
<Button
android:id="@+id/btn_main_request_picture_in_picture"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请画中画权限" />
<Button
android:id="@+id/btn_main_request_open_vpn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:text="申请 VPN 权限" />
<Button
android:id="@+id/btn_main_app_details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:layout_marginBottom="10dp"
android:text="跳转到应用详情页" />
</LinearLayout>
</FrameLayout>
</androidx.core.widget.NestedScrollView>
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_main_request_single).setOnClickListener(this);
findViewById(R.id.btn_main_request_group).setOnClickListener(this);
findViewById(R.id.btn_main_request_location).setOnClickListener(this);
findViewById(R.id.btn_main_request_sensors).setOnClickListener(this);
findViewById(R.id.btn_main_request_activity_recognition).setOnClickListener(this);
findViewById(R.id.btn_main_request_bluetooth).setOnClickListener(this);
findViewById(R.id.btn_main_request_wifi).setOnClickListener(this);
findViewById(R.id.btn_main_request_media_location).setOnClickListener(this);
findViewById(R.id.btn_main_request_media_storage).setOnClickListener(this);
findViewById(R.id.btn_main_request_manage_storage).setOnClickListener(this);
findViewById(R.id.btn_main_request_install).setOnClickListener(this);
findViewById(R.id.btn_main_request_window).setOnClickListener(this);
findViewById(R.id.btn_main_request_setting).setOnClickListener(this);
findViewById(R.id.btn_main_request_notification).setOnClickListener(this);
findViewById(R.id.btn_main_request_post_notification).setOnClickListener(this);
findViewById(R.id.btn_main_request_notification_listener).setOnClickListener(this);
findViewById(R.id.btn_main_request_package).setOnClickListener(this);
findViewById(R.id.btn_main_request_alarm).setOnClickListener(this);
findViewById(R.id.btn_main_request_not_disturb).setOnClickListener(this);
findViewById(R.id.btn_main_request_ignore_battery).setOnClickListener(this);
findViewById(R.id.btn_main_request_picture_in_picture).setOnClickListener(this);
findViewById(R.id.btn_main_request_open_vpn).setOnClickListener(this);
findViewById(R.id.btn_main_app_details).setOnClickListener(this);
}
@Override
public void onClick(View view) {
int viewId = view.getId();
if (viewId == R.id.btn_main_request_single) {
XXPermissions.with(this)
.permission(Permission.CAMERA)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
} else if (viewId == R.id.btn_main_request_group) {
XXPermissions.with(this)
.permission(Permission.RECORD_AUDIO)
.permission(Permission.Group.CALENDAR)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
} else if (viewId == R.id.btn_main_request_location) {
XXPermissions.with(this)
.permission(Permission.ACCESS_COARSE_LOCATION)
.permission(Permission.ACCESS_FINE_LOCATION)
// 如果不需要在后台使用定位功能,请不要申请此权限
.permission(Permission.ACCESS_BACKGROUND_LOCATION)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
} else if (viewId == R.id.btn_main_request_sensors) {
XXPermissions.with(this)
.permission(Permission.BODY_SENSORS)
.permission(Permission.BODY_SENSORS_BACKGROUND)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
} else if (viewId == R.id.btn_main_request_activity_recognition) {
XXPermissions.with(this)
.permission(Permission.ACTIVITY_RECOGNITION)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
addCountStepListener();
}
});
} else if (viewId == R.id.btn_main_request_bluetooth) {
long delayMillis = 0;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
delayMillis = 2000;
toast("当前版本不是 Android 12 及以上,旧版本的需要定位权限才能进行扫描蓝牙");
}
view.postDelayed(new Runnable() {
@Override
public void run() {
XXPermissions.with(MainActivity.this)
.permission(Permission.BLUETOOTH_SCAN)
.permission(Permission.BLUETOOTH_CONNECT)
.permission(Permission.BLUETOOTH_ADVERTISE)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
}
}, delayMillis);
} else if (viewId == R.id.btn_main_request_wifi) {
long delayMillis = 0;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
delayMillis = 2000;
toast("当前版本不是 Android 13 及以上,旧版本的需要定位权限才能进行扫描 WIFI");
}
view.postDelayed(new Runnable() {
@Override
public void run() {
XXPermissions.with(MainActivity.this)
.permission(Permission.NEARBY_WIFI_DEVICES)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
}
}, delayMillis);
} else if (viewId == R.id.btn_main_request_media_location) {
long delayMillis = 0;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
delayMillis = 2000;
toast("当前版本不是 Android 10 及以上,旧版本的需要读取存储权限才能获取媒体位置权限");
}
view.postDelayed(new Runnable() {
@Override
public void run() {
XXPermissions.with(MainActivity.this)
// Permission.READ_EXTERNAL_STORAGE 和 Permission.MANAGE_EXTERNAL_STORAGE 二选一
// 如果 targetSdk >= 33,则添加 Permission.READ_MEDIA_IMAGES 和 Permission.MANAGE_EXTERNAL_STORAGE 二选一
// 如果 targetSdk < 33,则添加 Permission.READ_EXTERNAL_STORAGE 和 Permission.MANAGE_EXTERNAL_STORAGE 二选一
.permission(Permission.READ_MEDIA_IMAGES)
.permission(Permission.ACCESS_MEDIA_LOCATION)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
new Thread(new Runnable() {
@Override
public void run() {
getAllImagesFromGallery();
}
}).start();
}
});
}
}, delayMillis);
} else if (viewId == R.id.btn_main_request_media_storage) {
long delayMillis = 0;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
delayMillis = 2000;
toast("当前版本不是 Android 13 及以上,会自动变更为旧版的请求方式");
}
view.postDelayed(new Runnable() {
@Override
public void run() {
XXPermissions.with(MainActivity.this)
// 不适配分区存储应该这样写
//.permission(Permission.MANAGE_EXTERNAL_STORAGE)
// 适配分区存储应该这样写
.permission(Permission.READ_MEDIA_IMAGES)
.permission(Permission.READ_MEDIA_VIDEO)
.permission(Permission.READ_MEDIA_AUDIO)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
}
}, delayMillis);
} else if (viewId == R.id.btn_main_request_manage_storage) {
long delayMillis = 0;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
delayMillis = 2000;
toast("当前版本不是 Android 11 及以上,会自动变更为旧版的请求方式");
}
view.postDelayed(new Runnable() {
@Override
public void run() {
XXPermissions.with(MainActivity.this)
// 适配分区存储应该这样写
//.permission(Permission.Group.STORAGE)
// 不适配分区存储应该这样写
.permission(Permission.MANAGE_EXTERNAL_STORAGE)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
}
}, delayMillis);
} else if (viewId == R.id.btn_main_request_install) {
XXPermissions.with(this)
.permission(Permission.REQUEST_INSTALL_PACKAGES)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
} else if (viewId == R.id.btn_main_request_window) {
XXPermissions.with(this)
.permission(Permission.SYSTEM_ALERT_WINDOW)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
} else if (viewId == R.id.btn_main_request_setting) {
XXPermissions.with(this)
.permission(Permission.WRITE_SETTINGS)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
} else if (viewId == R.id.btn_main_request_notification) {
XXPermissions.with(this)
.permission(Permission.NOTIFICATION_SERVICE)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
} else if (viewId == R.id.btn_main_request_post_notification) {
long delayMillis = 0;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
delayMillis = 2000;
toast("当前版本不是 Android 13 及以上,会自动变更为旧版的请求方式");
}
view.postDelayed(new Runnable() {
@Override
public void run() {
XXPermissions.with(MainActivity.this)
.permission(Permission.POST_NOTIFICATIONS)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
}
}, delayMillis);
} else if (viewId == R.id.btn_main_request_notification_listener) {
XXPermissions.with(this)
.permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
toggleNotificationListenerService();
}
}
});
} else if (viewId == R.id.btn_main_request_package) {
XXPermissions.with(this)
.permission(Permission.PACKAGE_USAGE_STATS)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
} else if (viewId == R.id.btn_main_request_alarm) {
XXPermissions.with(this)
.permission(Permission.SCHEDULE_EXACT_ALARM)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
} else if (viewId == R.id.btn_main_request_not_disturb) {
XXPermissions.with(this)
.permission(Permission.ACCESS_NOTIFICATION_POLICY)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
} else if (viewId == R.id.btn_main_request_ignore_battery) {
XXPermissions.with(this)
.permission(Permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
} else if (viewId == R.id.btn_main_request_picture_in_picture) {
XXPermissions.with(this)
.permission(Permission.PICTURE_IN_PICTURE)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
} else if (viewId == R.id.btn_main_request_open_vpn) {
XXPermissions.with(this)
.permission(Permission.BIND_VPN_SERVICE)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (!all) {
return;
}
toast("获取" + PermissionNameConvert.getPermissionString(MainActivity.this, permissions) + "成功");
}
});
} else if (viewId == R.id.btn_main_app_details) {
XXPermissions.startPermissionActivity(this);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == XXPermissions.REQUEST_CODE) {
toast("检测到你刚刚从权限设置界面返回回来");
}
}
public void toast(CharSequence text) {
ToastUtils.show(text);
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
private void toggleNotificationListenerService() {
PackageManager packageManager = getPackageManager();
packageManager.setComponentEnabledSetting(
new ComponentName(this, NotificationMonitorService.class),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
packageManager.setComponentEnabledSetting(
new ComponentName(this, NotificationMonitorService.class),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}
/**
* 获取所有图片
*/
private void getAllImagesFromGallery() {
String[] projection = {MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA,
MediaStore.MediaColumns.TITLE, MediaStore.Images.Media.SIZE,
MediaStore.Images.ImageColumns.LATITUDE, MediaStore.Images.ImageColumns.LONGITUDE};
final String orderBy = MediaStore.Video.Media.DATE_TAKEN;
Cursor cursor = getApplicationContext().getContentResolver()
.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection,
null, null, orderBy + " DESC");
int idIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID);
int pathIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
while (cursor.moveToNext()) {
String filePath = cursor.getString(pathIndex);
float[] latLong = new float[2];
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// 谷歌官方文档:https://developer.android.google.cn/training/data-storage/shared/media?hl=zh-cn#location-media-captured
Uri photoUri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
cursor.getString(idIndex));
photoUri = MediaStore.setRequireOriginal(photoUri);
try {
InputStream inputStream = getApplicationContext()
.getContentResolver().openInputStream(photoUri);
if (inputStream == null) {
continue;
}
ExifInterface exifInterface = new ExifInterface(inputStream);
// 获取图片的经纬度
exifInterface.getLatLong(latLong);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
} catch (UnsupportedOperationException e) {
// java.lang.UnsupportedOperationException:
// Caller must hold ACCESS_MEDIA_LOCATION permission to access original
// 经过测试,在部分手机上面申请获取媒体位置权限,如果用户选择的是 "仅在使用中允许"
// 那么就会导致权限是授予状态,但是调用 openInputStream 时会抛出此异常
e.printStackTrace();
}
} else {
int latitudeIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.LATITUDE);
int longitudeIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.LONGITUDE);
latLong = new float[]{cursor.getFloat(latitudeIndex), cursor.getFloat(longitudeIndex)};
}
if (latLong[0] != 0 && latLong[1] != 0) {
Log.i("XXPermissions", "获取到图片的经纬度:" + filePath + "," + Arrays.toString(latLong));
Log.i("XXPermissions", "图片经纬度所在的地址:" + latLongToAddressString(latLong[0], latLong[1]));
} else {
Log.i("XXPermissions", "该图片获取不到经纬度:" + filePath);
}
}
cursor.close();
}
/**
* 将经纬度转换成地址
*/
private String latLongToAddressString(float latitude, float longitude) {
String addressString = "";
Geocoder geocoder = new Geocoder(this, Locale.getDefault());
try {
List<Address> addresses = geocoder.getFromLocation(latitude, longitude, 1);
if (addresses != null) {
Address returnedAddress = addresses.get(0);
StringBuilder strReturnedAddress = new StringBuilder("");
for (int i = 0; i <= returnedAddress.getMaxAddressLineIndex(); i++) {
strReturnedAddress.append(returnedAddress.getAddressLine(i)).append("\n");
}
addressString = strReturnedAddress.toString();
} else {
Log.w("XXPermissions", "没有返回地址");
}
} catch (Exception e) {
e.printStackTrace();
Log.w("XXPermissions", "无法获取到地址");
}
return addressString;
}
private final SensorEventListener mSensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
Log.w("onSensorChanged", "event = " + event);
switch (event.sensor.getType()) {
case Sensor.TYPE_STEP_COUNTER:
Log.w("XXPermissions", "开机以来当天总步数:" + event.values[0]);
break;
case Sensor.TYPE_STEP_DETECTOR:
if (event.values[0] == 1) {
Log.w("XXPermissions", "当前走了一步");
}
break;
default:
break;
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
Log.w("onAccuracyChanged", String.valueOf(accuracy));
}
};
/**
* 添加步数监听
*/
private void addCountStepListener() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return;
}
SensorManager manager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor stepSensor = manager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
Sensor detectorSensor = manager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
if (stepSensor != null) {
manager.registerListener(mSensorEventListener, stepSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
if (detectorSensor != null) {
manager.registerListener(mSensorEventListener, detectorSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}
}