From e79b1f3967251d8fdebe829f042efac120dfcbba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Android=20=E8=BD=AE=E5=AD=90=E5=93=A5?= Date: Sun, 9 Apr 2023 20:37:50 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=E6=9D=83?= =?UTF-8?q?=E9=99=90=E8=AE=BE=E7=BD=AE=E9=A1=B5=E9=9D=A2=E8=B7=B3=E8=BD=AC?= =?UTF-8?q?=E5=85=9C=E5=BA=95=E6=9C=BA=E5=88=B6=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=90=8E=E5=8F=B0=E5=AE=9A=E4=BD=8D=E6=9D=83=E9=99=90=E5=92=8C?= =?UTF-8?q?=E5=90=8E=E5=8F=B0=E4=BC=A0=E6=84=9F=E5=99=A8=E6=9D=83=E9=99=90?= =?UTF-8?q?=E8=A2=AB=E6=8B=92=E7=BB=9D=E5=90=8E=E7=9A=84=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E6=96=87=E6=A1=88=20=E4=BC=98=E5=8C=96=20android=2011=20?= =?UTF-8?q?=E5=8F=8A=E4=BB=A5=E4=B8=8A=E7=9A=84=20miui=20=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E8=B7=B3=E8=BD=AC=E5=88=B0=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E6=8E=88=E6=9D=83=E9=A1=B5=E9=9D=A2=E9=80=BB=E8=BE=91=20?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=20android=206.0=20=E4=BB=A5=E4=B8=8B?= =?UTF-8?q?=E7=9A=84=E8=AE=BE=E5=A4=87=E7=94=B3=E8=AF=B7=E6=82=AC=E6=B5=AE?= =?UTF-8?q?=E7=AA=97=E6=9D=83=E9=99=90=20=E5=85=BC=E5=AE=B9=20android=206.?= =?UTF-8?q?0=20=E4=BB=A5=E4=B8=8B=E7=9A=84=E8=AE=BE=E5=A4=87=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E6=82=AC=E6=B5=AE=E7=AA=97=E6=9D=83=E9=99=90=E7=94=B3?= =?UTF-8?q?=E8=AF=B7=E9=A1=B5=E9=9D=A2=20=E5=85=BC=E5=AE=B9=20miui=2013=20?= =?UTF-8?q?=E4=BB=A5=E4=B8=8B=E7=9A=84=E8=AE=BE=E5=A4=87=E7=94=B3=E8=AF=B7?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=E5=BA=94=E7=94=A8=E5=88=97=E8=A1=A8=E6=9D=83?= =?UTF-8?q?=E9=99=90=20=E4=BF=AE=E6=AD=A3=E9=80=9A=E7=9F=A5=E6=A0=8F?= =?UTF-8?q?=E6=9D=83=E9=99=90=E5=9C=A8=20Android=205.0=20=E7=9A=84?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E8=B7=B3=E8=BD=AC=E5=88=B0=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E9=A1=B5=E9=9D=A2=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20=E4=BF=AE=E6=AD=A3=20targetSdk=20>=3D=2033=20=E7=94=B3?= =?UTF-8?q?=E8=AF=B7=E8=AF=BB=E5=8F=96=E5=A4=96=E9=83=A8=E5=AD=98=E5=82=A8?= =?UTF-8?q?=E6=9D=83=E9=99=90=E7=9A=84=E5=90=91=E4=B8=8B=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E5=88=A4=E6=96=AD=20=E4=BF=AE=E6=AD=A3=20android=2010=20?= =?UTF-8?q?=E5=8F=8A=E4=BB=A5=E4=B8=8B=E8=AE=BE=E5=A4=87=E7=94=B3=E8=AF=B7?= =?UTF-8?q?=E6=89=80=E6=9C=89=E6=96=87=E4=BB=B6=E7=AE=A1=E7=90=86=E6=9D=83?= =?UTF-8?q?=E9=99=90=E4=BC=9A=E5=A4=9A=E5=BC=B9=E5=87=BA=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=A1=86=E7=9A=84=E9=97=AE=E9=A2=98=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9D=83=E9=99=90=E6=A3=80=E6=B5=8B=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=E4=B8=AD=E7=9A=84=E6=B8=85=E5=8D=95=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=A3=80=E6=B5=8B=E9=80=BB=E8=BE=91=E7=9A=84?= =?UTF-8?q?=20Bug=20=E4=BF=AE=E5=A4=8D=E9=80=9A=E7=9F=A5=E6=A0=8F=E7=9B=91?= =?UTF-8?q?=E5=90=AC=E6=9D=83=E9=99=90=E5=AF=B9=E5=BA=94=20Service=20?= =?UTF-8?q?=E7=B1=BB=E5=90=8D=E8=A2=AB=E4=BF=AE=E6=94=B9=E5=90=8E=E4=BB=8D?= =?UTF-8?q?=E7=84=B6=E6=8E=88=E6=9D=83=E7=9A=84=20Bug=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=B8=BF=E8=92=99=202.0=20=E5=8F=8A=E4=BB=A5=E4=B8=8A=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E8=B7=B3=E8=BD=AC=E5=88=B0=E5=8B=BF=E6=89=B0=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E8=AE=BE=E7=BD=AE=E7=95=8C=E9=9D=A2=E4=BC=9A=E5=87=BA?= =?UTF-8?q?=E7=8E=B0=E5=B4=A9=E6=BA=83=E7=9A=84=20Bug=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20PermissionUtils=20=E5=88=A4=E6=96=AD=20Activity=20=E6=84=8F?= =?UTF-8?q?=E5=9B=BE=E6=98=AF=E5=90=A6=E5=AD=98=E5=9C=A8=E5=9C=A8=E6=9F=90?= =?UTF-8?q?=E4=BA=9B=E6=89=8B=E6=9C=BA=E4=BC=9A=E5=87=BA=E7=8E=B0=E8=AF=AF?= =?UTF-8?q?=E5=88=A4=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ISSUE_TEMPLATE/issue_en_template_bug.md | 2 + .../ISSUE_TEMPLATE/issue_zh_template_bug.md | 2 + README-en.md | 8 +- README.md | 14 +- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 1 + .../demo/NotificationMonitorService.java | 22 +- .../demo/PermissionInterceptor.java | 19 +- .../main/res/values-zh/strings_permission.xml | 5 +- app/src/main/res/values/string_permission.xml | 5 +- library/build.gradle | 4 +- library/src/main/AndroidManifest.xml | 1 + .../com/hjq/permissions/AndroidVersion.java | 7 + .../GetInstalledAppsPermissionCompat.java | 134 ++++++++ .../NotificationListenerPermissionCompat.java | 96 ++++++ .../NotificationPermissionCompat.java | 48 +++ .../com/hjq/permissions/PermissionApi.java | 6 + .../hjq/permissions/PermissionChecker.java | 7 +- .../PermissionDelegateImplV14.java | 181 +---------- .../PermissionDelegateImplV18.java | 42 +++ .../PermissionDelegateImplV19.java | 90 ++++++ .../PermissionDelegateImplV21.java | 67 ++++ .../PermissionDelegateImplV23.java | 185 +++-------- .../PermissionDelegateImplV26.java | 11 +- .../PermissionDelegateImplV33.java | 36 ++- .../hjq/permissions/PermissionFragment.java | 2 +- .../permissions/PermissionIntentManager.java | 285 +++++++++++++++++ .../permissions/PermissionPageFragment.java | 2 +- .../com/hjq/permissions/PermissionUtils.java | 113 +++++-- .../com/hjq/permissions/PhoneRomUtils.java | 302 ++++++++++++++++++ .../hjq/permissions/StartActivityManager.java | 193 +++++++++++ .../permissions/WindowPermissionCompat.java | 103 ++++++ .../com/hjq/permissions/XXPermissions.java | 18 +- 33 files changed, 1609 insertions(+), 408 deletions(-) create mode 100644 library/src/main/java/com/hjq/permissions/GetInstalledAppsPermissionCompat.java create mode 100644 library/src/main/java/com/hjq/permissions/NotificationListenerPermissionCompat.java create mode 100644 library/src/main/java/com/hjq/permissions/NotificationPermissionCompat.java create mode 100644 library/src/main/java/com/hjq/permissions/PermissionDelegateImplV18.java create mode 100644 library/src/main/java/com/hjq/permissions/PermissionDelegateImplV19.java create mode 100644 library/src/main/java/com/hjq/permissions/PermissionDelegateImplV21.java create mode 100644 library/src/main/java/com/hjq/permissions/PermissionIntentManager.java create mode 100644 library/src/main/java/com/hjq/permissions/PhoneRomUtils.java create mode 100644 library/src/main/java/com/hjq/permissions/StartActivityManager.java create mode 100644 library/src/main/java/com/hjq/permissions/WindowPermissionCompat.java diff --git a/.github/ISSUE_TEMPLATE/issue_en_template_bug.md b/.github/ISSUE_TEMPLATE/issue_en_template_bug.md index 1bb115f..b6fec3e 100644 --- a/.github/ISSUE_TEMPLATE/issue_en_template_bug.md +++ b/.github/ISSUE_TEMPLATE/issue_en_template_bug.md @@ -19,6 +19,8 @@ assignees: getActivity * Whether the problem can be reproduced [Required]: Yes/No +* Android Project targetSdkVersion [Required]: Please fill in your project target sdk version + * Phone information in question [Required]: Please fill in the phone brand and model in question * Android version in question [Required]: Please fill in the Android version in question diff --git a/.github/ISSUE_TEMPLATE/issue_zh_template_bug.md b/.github/ISSUE_TEMPLATE/issue_zh_template_bug.md index 39e6817..1fbcf66 100644 --- a/.github/ISSUE_TEMPLATE/issue_zh_template_bug.md +++ b/.github/ISSUE_TEMPLATE/issue_zh_template_bug.md @@ -18,6 +18,8 @@ assignees: getActivity * 是否必现【必填】:是/否 +* 项目 targetSdkVersion【必填】:XX + * 出现问题的手机信息【必填】:请填写出现问题的品牌和机型 * 出现问题的安卓版本【必填】:请填写出现问题的 Android 版本 diff --git a/README-en.md b/README-en.md index ee64295..1a87fbf 100644 --- a/README-en.md +++ b/README-en.md @@ -6,7 +6,7 @@ * project address: [Github](https://github.com/getActivity/XXPermissions) -* [Click here to download demo apk directly](https://github.com/getActivity/XXPermissions/releases/download/16.8/XXPermissions.apk) +* [Click here to download demo apk directly](https://github.com/getActivity/XXPermissions/releases/download/18.0/XXPermissions.apk) ![](picture/en/demo_request_permission_activity.jpg) ![](picture/en/demo_request_single_permission.jpg) ![](picture/en/demo_request_group_permission.jpg) @@ -55,7 +55,7 @@ android { dependencies { // Permission request framework:https://github.com/getActivity/XXPermissions - implementation 'com.github.getActivity:XXPermissions:16.8' + implementation 'com.github.getActivity:XXPermissions:18.0' } ``` @@ -209,9 +209,9 @@ XXPermissions.setInterceptor(new IPermissionInterceptor() {}); | Adaptation details | [XXPermissions](https://github.com/getActivity/XXPermissions) | [AndPermission](https://github.com/yanzhenjie/AndPermission) | [PermissionX](https://github.com/guolindev/PermissionX) | [AndroidUtilCode-PermissionUtils](https://github.com/Blankj/AndroidUtilCode) | [PermissionsDispatcher](https://github.com/permissions-dispatcher/PermissionsDispatcher) | [RxPermissions](https://github.com/tbruyelle/RxPermissions) | [EasyPermissions](https://github.com/googlesamples/easypermissions) | | :-----------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------: | -| Corresponding version | 16.8 | 2.0.3 | 1.7.1 | 1.31.0 | 4.9.2 | 0.12 | 3.0.0 | +| Corresponding version | 18.0 | 2.0.3 | 1.7.1 | 1.31.0 | 4.9.2 | 0.12 | 3.0.0 | | Number of issues | [![](https://img.shields.io/github/issues/getActivity/XXPermissions.svg)](https://github.com/getActivity/XXPermissions/issues) | [![](https://img.shields.io/github/issues/yanzhenjie/AndPermission.svg)](https://github.com/yanzhenjie/AndPermission/issues) | [![](https://img.shields.io/github/issues/guolindev/PermissionX.svg)](https://github.com/guolindev/PermissionX/issues) | [![](https://img.shields.io/github/issues/Blankj/AndroidUtilCode.svg)](https://github.com/Blankj/AndroidUtilCode/issues) | [![](https://img.shields.io/github/issues/permissions-dispatcher/PermissionsDispatcher.svg)](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues) | [![](https://img.shields.io/github/issues/tbruyelle/RxPermissions.svg)](https://github.com/tbruyelle/RxPermissions/issues) | [![](https://img.shields.io/github/issues/googlesamples/easypermissions.svg)](https://github.com/googlesamples/easypermissions/issues) | -| Framework volume | 60 KB | 127 KB | 97 KB | 500 KB | 99 KB | 28 KB | 48 KB | +| Framework volume | 82 KB | 127 KB | 97 KB | 500 KB | 99 KB | 28 KB | 48 KB | | Framework Maintenance Status | **In maintenance** | stop maintenance | **In maintenance** | stop maintenance | stop maintenance | stop maintenance | stop maintenance | | Alarm reminder permission | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | All file management permissions | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | diff --git a/README.md b/README.md index 98c9218..0949b67 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ * 博文地址:[一句代码搞定权限请求,从未如此简单](https://www.jianshu.com/p/c69ff8a445ed) -* 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](https://github.com/getActivity/XXPermissions/releases/download/16.8/XXPermissions.apk) +* 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](https://github.com/getActivity/XXPermissions/releases/download/18.0/XXPermissions.apk) ![](picture/zh/download_demo_apk_qr_code.png) @@ -61,7 +61,7 @@ android { dependencies { // 权限请求框架:https://github.com/getActivity/XXPermissions - implementation 'com.github.getActivity:XXPermissions:16.8' + implementation 'com.github.getActivity:XXPermissions:18.0' } ``` @@ -215,9 +215,9 @@ XXPermissions.setInterceptor(new IPermissionInterceptor() {}); | 适配细节 | [XXPermissions](https://github.com/getActivity/XXPermissions) | [AndPermission](https://github.com/yanzhenjie/AndPermission) | [PermissionX](https://github.com/guolindev/PermissionX) | [AndroidUtilCode-PermissionUtils](https://github.com/Blankj/AndroidUtilCode) | [PermissionsDispatcher](https://github.com/permissions-dispatcher/PermissionsDispatcher) | [RxPermissions](https://github.com/tbruyelle/RxPermissions) | [EasyPermissions](https://github.com/googlesamples/easypermissions) | | :--------: | :------------: | :------------: | :------------: | :------------: | :------------: | :------------: | :------------: | -| 对应版本 | 16.8 | 2.0.3 | 1.7.1 | 1.31.0 | 4.9.2 | 0.12 | 3.0.0 | +| 对应版本 | 18.0 | 2.0.3 | 1.7.1 | 1.31.0 | 4.9.2 | 0.12 | 3.0.0 | | issues 数 | [![](https://img.shields.io/github/issues/getActivity/XXPermissions.svg)](https://github.com/getActivity/XXPermissions/issues) | [![](https://img.shields.io/github/issues/yanzhenjie/AndPermission.svg)](https://github.com/yanzhenjie/AndPermission/issues) | [![](https://img.shields.io/github/issues/guolindev/PermissionX.svg)](https://github.com/guolindev/PermissionX/issues) | [![](https://img.shields.io/github/issues/Blankj/AndroidUtilCode.svg)](https://github.com/Blankj/AndroidUtilCode/issues) | [![](https://img.shields.io/github/issues/permissions-dispatcher/PermissionsDispatcher.svg)](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues) | [![](https://img.shields.io/github/issues/tbruyelle/RxPermissions.svg)](https://github.com/tbruyelle/RxPermissions/issues) | [![](https://img.shields.io/github/issues/googlesamples/easypermissions.svg)](https://github.com/googlesamples/easypermissions/issues) | -| 框架体积 | 60 KB | 127 KB | 97 KB | 500 KB | 99 KB | 28 KB | 48 KB | +| 框架体积 | 82 KB | 127 KB | 97 KB | 500 KB | 99 KB | 28 KB | 48 KB | | 框架维护状态 |**维护中**| 停止维护 | 停止维护 | 停止维护 | 停止维护 | 停止维护 | 停止维护 | | 闹钟提醒权限 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 所有文件管理权限 | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | @@ -269,7 +269,9 @@ XXPermissions.setInterceptor(new IPermissionInterceptor() {}); | 奇虎 | 360 手机 N7 Lite | 360 Os 3.0 && Android 8.1 | 否 | | 小辣椒 | 小辣椒S6 | 小辣椒 Os 3.0 && Android 7.1.1 | 否 | -* 另外还有一些厂商没有列出来,不是我没有做测试,而是他们的系统本身就是直接用 Android 的,Android 原生目前不支持申请该权限 +* 还有一些厂商没有列出来,并不是作者没有做测试,而是他们的系统本身就是直接用 Android 的,Android 原生目前不支持申请该权限 + +* 另外对于 miui 的设备,这套机制只支持 miui 13 及以上的版本,但是框架做了一些兼容手段,目前已经适配了所有 miui 版本读取应用列表权限的申请 #### 新权限自动兼容旧设备介绍 @@ -407,6 +409,8 @@ XXPermissions.setInterceptor(new IPermissionInterceptor() {}); * 表情包大集合:[EmojiPackage](https://github.com/getActivity/EmojiPackage) ![](https://img.shields.io/github/stars/getActivity/EmojiPackage.svg) ![](https://img.shields.io/github/forks/getActivity/EmojiPackage.svg) +* AI 资源大汇总:[AiIndex](https://github.com/getActivity/AiIndex) ![](https://img.shields.io/github/stars/getActivity/AiIndex.svg) ![](https://img.shields.io/github/forks/getActivity/AiIndex.svg) + * 省市区 Json 数据:[ProvinceJson](https://github.com/getActivity/ProvinceJson) ![](https://img.shields.io/github/stars/getActivity/ProvinceJson.svg) ![](https://img.shields.io/github/forks/getActivity/ProvinceJson.svg) * Markdown 语法文档:[MarkdownDoc](https://github.com/getActivity/MarkdownDoc) ![](https://img.shields.io/github/stars/getActivity/MarkdownDoc.svg) ![](https://img.shields.io/github/forks/getActivity/MarkdownDoc.svg) diff --git a/app/build.gradle b/app/build.gradle index c44fe26..b523663 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.hjq.permissions.demo" minSdkVersion 16 targetSdkVersion 33 - versionCode 1680 - versionName "16.8" + versionCode 1800 + versionName "18.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } @@ -61,7 +61,7 @@ dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' // 吐司框架:https://github.com/getActivity/Toaster - implementation 'com.github.getActivity:Toaster:12.0' + implementation 'com.github.getActivity:Toaster:12.2' // 内存泄漏检测:https://github.com/square/leakcanary debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9048c75..19574f8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -95,6 +95,7 @@ diff --git a/app/src/main/java/com/hjq/permissions/demo/NotificationMonitorService.java b/app/src/main/java/com/hjq/permissions/demo/NotificationMonitorService.java index 859bd56..72d53da 100644 --- a/app/src/main/java/com/hjq/permissions/demo/NotificationMonitorService.java +++ b/app/src/main/java/com/hjq/permissions/demo/NotificationMonitorService.java @@ -24,16 +24,20 @@ public final class NotificationMonitorService extends NotificationListenerServic @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); - Toaster.show(String.format(getString(R.string.demo_notification_listener_toast), title, msgText)); - } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return; } + + Bundle extras = sbn.getNotification().extras; + if (extras == null) { + return; + } + + //获取通知消息标题 + String title = extras.getString(Notification.EXTRA_TITLE); + // 获取通知消息内容 + Object msgText = extras.getCharSequence(Notification.EXTRA_TEXT); + Toaster.show(String.format(getString(R.string.demo_notification_listener_toast), title, msgText)); } /** diff --git a/app/src/main/java/com/hjq/permissions/demo/PermissionInterceptor.java b/app/src/main/java/com/hjq/permissions/demo/PermissionInterceptor.java index eaf4d33..b6943d1 100644 --- a/app/src/main/java/com/hjq/permissions/demo/PermissionInterceptor.java +++ b/app/src/main/java/com/hjq/permissions/demo/PermissionInterceptor.java @@ -10,6 +10,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.app.AlertDialog; +import android.text.TextUtils; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; @@ -62,6 +63,10 @@ public void launchPermissionRequest(@NonNull Activity activity, @NonNull List(allPermissions), this, callback); // 延迟 300 毫秒是为了避免出现 PopupWindow 显示然后立马消失的情况 // 因为框架没有办法在还没有申请权限的情况下,去判断权限是否永久拒绝了,必须要在发起权限申请之后 @@ -84,7 +88,6 @@ public void launchPermissionRequest(@NonNull Activity activity, @NonNull List= Build.VERSION_CODES.R) { + backgroundPermissionOptionLabel = String.valueOf(activity.getPackageManager().getBackgroundPermissionOptionLabel()); + } + if (TextUtils.isEmpty(backgroundPermissionOptionLabel)) { + backgroundPermissionOptionLabel = activity.getString(R.string.common_permission_background_default_option_label); + } + if (Permission.ACCESS_BACKGROUND_LOCATION.equals(deniedPermission)) { - Toaster.show(R.string.common_permission_background_location_fail_hint); + Toaster.show(activity.getString(R.string.common_permission_background_location_fail_hint, backgroundPermissionOptionLabel)); return; } if (Permission.BODY_SENSORS_BACKGROUND.equals(deniedPermission)) { - Toaster.show(R.string.common_permission_background_sensors_fail_hint); + Toaster.show(activity.getString(R.string.common_permission_background_sensors_fail_hint, backgroundPermissionOptionLabel)); return; } } diff --git a/app/src/main/res/values-zh/strings_permission.xml b/app/src/main/res/values-zh/strings_permission.xml index fce7ee4..b5a37b4 100644 --- a/app/src/main/res/values-zh/strings_permission.xml +++ b/app/src/main/res/values-zh/strings_permission.xml @@ -11,8 +11,9 @@ 授权失败,请正确授予%s 获取权限失败,请手动授予权限 获取权限失败,请手动授予%s - 获取后台定位权限失败,\n请您选择《始终允许》 - 获取后台传感器权限失败,\n请您选择《始终允许》 + 获取后台定位权限失败,\n请您选择《%s》选项 + 获取后台传感器权限失败,\n请您选择《%s》选项 + 始终允许 获取媒体位置权限失败\n请清除应用数据后重试 前往授权 diff --git a/app/src/main/res/values/string_permission.xml b/app/src/main/res/values/string_permission.xml index 42b1c2b..f09af4c 100644 --- a/app/src/main/res/values/string_permission.xml +++ b/app/src/main/res/values/string_permission.xml @@ -11,8 +11,9 @@ Authorization failed, please grant %s correctly Failed to obtain permission, please grant permission manually Failed to obtain permission, please grant %s manually - Failed to obtain background location permission,\nPlease select "Allow all the time" - Failed to obtain background sensor permission.\nPlease select "Allow all the time" + Failed to obtain background location permission,\nPlease select "%s" option + Failed to obtain background sensor permission.\nPlease select "%s" option + Allow all the time Failed to get media location permission\nPlease clear app data and try again Go to authorization diff --git a/library/build.gradle b/library/build.gradle index 55878e7..825d647 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -5,8 +5,8 @@ android { defaultConfig { minSdkVersion 14 - versionCode 1680 - versionName "16.8" + versionCode 1800 + versionName "18.0" } // 使用 JDK 1.8 diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml index 7ddd234..e2b3546 100644 --- a/library/src/main/AndroidManifest.xml +++ b/library/src/main/AndroidManifest.xml @@ -1 +1,2 @@ + \ No newline at end of file diff --git a/library/src/main/java/com/hjq/permissions/AndroidVersion.java b/library/src/main/java/com/hjq/permissions/AndroidVersion.java index 9ff1153..5e62290 100644 --- a/library/src/main/java/com/hjq/permissions/AndroidVersion.java +++ b/library/src/main/java/com/hjq/permissions/AndroidVersion.java @@ -124,6 +124,13 @@ static boolean isAndroid5() { return Build.VERSION.SDK_INT >= ANDROID_5; } + /** + * 是否是 Android 4.4 及以上版本 + */ + static boolean isAndroid4_4() { + return Build.VERSION.SDK_INT >= ANDROID_4_4; + } + /** * 是否是 Android 4.3 及以上版本 */ diff --git a/library/src/main/java/com/hjq/permissions/GetInstalledAppsPermissionCompat.java b/library/src/main/java/com/hjq/permissions/GetInstalledAppsPermissionCompat.java new file mode 100644 index 0000000..b14cd74 --- /dev/null +++ b/library/src/main/java/com/hjq/permissions/GetInstalledAppsPermissionCompat.java @@ -0,0 +1,134 @@ +package com.hjq.permissions; + +import android.app.Activity; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/XXPermissions + * time : 2023/03/12 + * desc : 读取应用列表权限兼容类 + */ +final class GetInstalledAppsPermissionCompat { + + private static final String MIUI_OP_GET_INSTALLED_APPS_FIELD_NAME = "OP_GET_INSTALLED_APPS"; + private static final int MIUI_OP_GET_INSTALLED_APPS_DEFAULT_VALUE = 10022; + + static boolean isGrantedPermission(@NonNull Context context) { + if (!AndroidVersion.isAndroid4_4()) { + return true; + } + + if (AndroidVersion.isAndroid6() && isSupportGetInstalledAppsPermission(context)) { + return PermissionUtils.checkSelfPermission(context, Permission.GET_INSTALLED_APPS); + } + + if (PhoneRomUtils.isMiui() && isMiuiSupportGetInstalledAppsPermission()) { + if (!PhoneRomUtils.isMiuiOptimization()) { + // 如果当前没有开启 miui 优化,则直接返回 true,表示已经授权,因为在这种情况下 + // 就算跳转 miui 权限设置页,用户也授权了,用代码判断权限还是没有授予的状态 + // 所以在没有开启 miui 优化的情况下,就告诉外层已经授予了,避免外层去引导用户跳转到权限设置页 + return true; + } + // 经过测试发现,OP_GET_INSTALLED_APPS 是小米在 Android 6.0 才加上的,看了 Android 5.0 的 miui 并没有出现读取应用列表的权限 + return PermissionUtils.checkOpNoThrow(context, MIUI_OP_GET_INSTALLED_APPS_FIELD_NAME, MIUI_OP_GET_INSTALLED_APPS_DEFAULT_VALUE); + } + + // 如果不支持申请,则直接返回 true(代表有这个权限),反正也不会崩溃,顶多就是获取不到第三方应用列表 + return true; + } + + static boolean isPermissionPermanentDenied(@NonNull Activity activity) { + if (!AndroidVersion.isAndroid4_4()) { + return false; + } + + if (AndroidVersion.isAndroid6() && isSupportGetInstalledAppsPermission(activity)) { + // 如果支持申请,那么再去判断权限是否永久拒绝 + return !PermissionUtils.checkSelfPermission(activity, Permission.GET_INSTALLED_APPS) && + !PermissionUtils.shouldShowRequestPermissionRationale(activity, Permission.GET_INSTALLED_APPS); + } + + if (PhoneRomUtils.isMiui() && isMiuiSupportGetInstalledAppsPermission()) { + if (!PhoneRomUtils.isMiuiOptimization()) { + return false; + } + // 如果在没有授权的情况下返回 true 表示永久拒绝,这样就能走后面的判断,让外层调用者跳转到小米定制的权限设置页面 + return !isGrantedPermission(activity); + } + + // 如果不支持申请,则直接返回 false(代表没有永久拒绝) + return false; + } + + static Intent getPermissionIntent(@NonNull Context context) { + if (PhoneRomUtils.isMiui()) { + Intent intent = null; + if (PhoneRomUtils.isMiuiOptimization()) { + intent = PermissionIntentManager.getMiuiPermissionPageIntent(context); + } + // 另外跳转到应用详情页也可以开启读取应用列表权限 + intent = StartActivityManager.addSubIntentToMainIntent(intent, PermissionUtils.getApplicationDetailsIntent(context)); + return intent; + } + + return PermissionUtils.getApplicationDetailsIntent(context); + } + + /** + * 判断是否支持获取应用列表权限 + */ + @RequiresApi(api = AndroidVersion.ANDROID_6) + private static boolean isSupportGetInstalledAppsPermission(Context context) { + try { + PermissionInfo permissionInfo = context.getPackageManager().getPermissionInfo(Permission.GET_INSTALLED_APPS, 0); + if (permissionInfo != null) { + if (AndroidVersion.isAndroid9()) { + return permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS; + } else { + return (permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) == PermissionInfo.PROTECTION_DANGEROUS; + } + } + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + + try { + // 移动终端应用软件列表权限实施指南:http://www.taf.org.cn/upload/AssociationStandard/TTAF%20108-2022%20%E7%A7%BB%E5%8A%A8%E7%BB%88%E7%AB%AF%E5%BA%94%E7%94%A8%E8%BD%AF%E4%BB%B6%E5%88%97%E8%A1%A8%E6%9D%83%E9%99%90%E5%AE%9E%E6%96%BD%E6%8C%87%E5%8D%97.pdf + // 这是兜底方案,因为测试了大量的机型,除了荣耀的 Magic UI 有按照这个规范去做,其他厂商(包括华为的 HarmonyOS)都没有按照这个规范去做 + // 虽然可以只用上面那种判断权限是不是危险权限的方式,但是避免不了有的手机厂商用下面的这种,所以两种都写比较好,小孩子才做选择,大人我全都要 + return Settings.Secure.getInt(context.getContentResolver(), "oem_installed_apps_runtime_permission_enable") == 1; + } catch (Settings.SettingNotFoundException e) { + e.printStackTrace(); + } + + return false; + } + + /** + * 判断当前 miui 版本是否支持申请读取应用列表权限 + */ + private static boolean isMiuiSupportGetInstalledAppsPermission() { + if (!AndroidVersion.isAndroid4_4()) { + return true; + } + try { + Class appOpsClass = Class.forName(AppOpsManager.class.getName()); + appOpsClass.getDeclaredField(MIUI_OP_GET_INSTALLED_APPS_FIELD_NAME); + // 证明有这个字段,返回 true + return true; + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + return true; + } +} \ No newline at end of file diff --git a/library/src/main/java/com/hjq/permissions/NotificationListenerPermissionCompat.java b/library/src/main/java/com/hjq/permissions/NotificationListenerPermissionCompat.java new file mode 100644 index 0000000..e24eb3d --- /dev/null +++ b/library/src/main/java/com/hjq/permissions/NotificationListenerPermissionCompat.java @@ -0,0 +1,96 @@ +package com.hjq.permissions; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.text.TextUtils; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/XXPermissions + * time : 2023/03/12 + * desc : 通知栏监听权限兼容类 + */ +final class NotificationListenerPermissionCompat { + + /** Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */ + private static final String SETTING_ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners"; + + static boolean isGrantedPermission(@NonNull Context context) { + // 经过实践得出,通知监听权限是在 Android 4.3 才出现的,所以前面的版本统一返回 true + if (!AndroidVersion.isAndroid4_3()) { + return true; + } + final String enabledNotificationListeners = Settings.Secure.getString( + context.getContentResolver(), SETTING_ENABLED_NOTIFICATION_LISTENERS); + if (TextUtils.isEmpty(enabledNotificationListeners)) { + return false; + } + // com.hjq.permissions.demo/com.hjq.permissions.demo.NotificationMonitorService:com.huawei.health/com.huawei.bone.ui.setting.NotificationPushListener + final String[] components = enabledNotificationListeners.split(":"); + for (String component : components) { + ComponentName componentName = ComponentName.unflattenFromString(component); + if (!TextUtils.equals(componentName.getPackageName(), context.getPackageName())) { + continue; + } + + String className = componentName.getClassName(); + try { + // 判断这个类有是否存在,如果存在的话,证明是有效的 + // 如果不存在的话,证明无效的,也是需要重新授权的 + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + return false; + } + + static Intent getPermissionIntent(@NonNull Context context) { + Intent intent = null; + if (AndroidVersion.isAndroid11()) { + AndroidManifestInfo androidManifestInfo = PermissionUtils.getAndroidManifestInfo(context); + AndroidManifestInfo.ServiceInfo serviceInfo = null; + if (androidManifestInfo != null) { + for (AndroidManifestInfo.ServiceInfo info : androidManifestInfo.serviceInfoList) { + if (!TextUtils.equals(info.permission, Permission.BIND_NOTIFICATION_LISTENER_SERVICE)) { + continue; + } + + if (serviceInfo != null) { + // 证明有两个这样的 Service,就不跳转到权限详情页了,而是跳转到权限列表页 + serviceInfo = null; + break; + } + + serviceInfo = info; + } + } + if (serviceInfo != null) { + intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS); + intent.putExtra(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME, + new ComponentName(context, serviceInfo.name).flattenToString()); + if (!PermissionUtils.areActivityIntent(context, intent)) { + intent = null; + } + } + } + + if (intent == null) { + if (AndroidVersion.isAndroid5_1()) { + intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS); + } else { + // android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS + intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"); + } + } + + if (!PermissionUtils.areActivityIntent(context, intent)) { + intent = PermissionUtils.getApplicationDetailsIntent(context); + } + return intent; + } +} \ No newline at end of file diff --git a/library/src/main/java/com/hjq/permissions/NotificationPermissionCompat.java b/library/src/main/java/com/hjq/permissions/NotificationPermissionCompat.java new file mode 100644 index 0000000..f64c406 --- /dev/null +++ b/library/src/main/java/com/hjq/permissions/NotificationPermissionCompat.java @@ -0,0 +1,48 @@ +package com.hjq.permissions; + +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; +import android.support.annotation.NonNull; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/XXPermissions + * time : 2023/03/12 + * desc : 通知栏权限兼容类 + */ +final class NotificationPermissionCompat { + + private static final String OP_POST_NOTIFICATION_FIELD_NAME = "OP_POST_NOTIFICATION"; + private static final int OP_POST_NOTIFICATION_DEFAULT_VALUE = 11; + + static boolean isGrantedPermission(@NonNull Context context) { + if (AndroidVersion.isAndroid7()) { + return context.getSystemService(NotificationManager.class).areNotificationsEnabled(); + } + + if (AndroidVersion.isAndroid4_4()) { + return PermissionUtils.checkOpNoThrow(context, OP_POST_NOTIFICATION_FIELD_NAME, OP_POST_NOTIFICATION_DEFAULT_VALUE); + } + return true; + } + + static Intent getPermissionIntent(@NonNull Context context) { + Intent intent = null; + if (AndroidVersion.isAndroid8()) { + intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); + intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()); + //intent.putExtra(Settings.EXTRA_CHANNEL_ID, context.getApplicationInfo().uid); + } else if (AndroidVersion.isAndroid5()) { + intent = new Intent(); + intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS"); + intent.putExtra("app_package", context.getPackageName()); + intent.putExtra("app_uid", context.getApplicationInfo().uid); + } + if (!PermissionUtils.areActivityIntent(context, intent)) { + intent = PermissionUtils.getApplicationDetailsIntent(context); + } + return intent; + } +} \ No newline at end of file diff --git a/library/src/main/java/com/hjq/permissions/PermissionApi.java b/library/src/main/java/com/hjq/permissions/PermissionApi.java index bae59ea..40eb790 100644 --- a/library/src/main/java/com/hjq/permissions/PermissionApi.java +++ b/library/src/main/java/com/hjq/permissions/PermissionApi.java @@ -35,6 +35,12 @@ final class PermissionApi { DELEGATE = new PermissionDelegateImplV26(); } else if (AndroidVersion.isAndroid6()) { DELEGATE = new PermissionDelegateImplV23(); + } else if (AndroidVersion.isAndroid5()) { + DELEGATE = new PermissionDelegateImplV21(); + } else if (AndroidVersion.isAndroid4_4()) { + DELEGATE = new PermissionDelegateImplV19(); + } else if (AndroidVersion.isAndroid4_3()) { + DELEGATE = new PermissionDelegateImplV18(); } else { DELEGATE = new PermissionDelegateImplV14(); } diff --git a/library/src/main/java/com/hjq/permissions/PermissionChecker.java b/library/src/main/java/com/hjq/permissions/PermissionChecker.java index a29f3ad..f666e36 100644 --- a/library/src/main/java/com/hjq/permissions/PermissionChecker.java +++ b/library/src/main/java/com/hjq/permissions/PermissionChecker.java @@ -502,10 +502,13 @@ static void checkManifestPermissions(@NonNull Context context, @NonNull List packageNames = NotificationManagerCompat.getEnabledListenerPackages(context); - return packageNames.contains(context.getPackageName()); - } - return true; - } - - /** - * 获取通知监听设置界面意图 - */ - private static Intent getNotificationListenerIntent(@NonNull Context context) { - Intent intent = null; - if (AndroidVersion.isAndroid11()) { - AndroidManifestInfo androidManifestInfo = PermissionUtils.getAndroidManifestInfo(context); - AndroidManifestInfo.ServiceInfo serviceInfo = null; - if (androidManifestInfo != null) { - for (AndroidManifestInfo.ServiceInfo info : androidManifestInfo.serviceInfoList) { - if (!TextUtils.equals(info.permission, Permission.BIND_NOTIFICATION_LISTENER_SERVICE)) { - continue; - } - - if (serviceInfo != null) { - // 证明有两个这样的 Service,就不跳转到权限详情页了,而是跳转到权限列表页 - serviceInfo = null; - break; - } - - serviceInfo = info; - } - } - if (serviceInfo != null) { - intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS); - intent.putExtra(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME, - new ComponentName(context, serviceInfo.name).flattenToString()); - if (!PermissionUtils.areActivityIntent(context, intent)) { - intent = null; - } - } - } - - if (intent == null) { - if (AndroidVersion.isAndroid5_1()) { - intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS); - } else { - // android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS - intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"); - } - } - - if (!PermissionUtils.areActivityIntent(context, intent)) { - intent = PermissionUtils.getApplicationDetailsIntent(context); - } - return intent; - } - - /** - * 是否有使用统计权限 - */ - private static boolean isGrantedPackagePermission(@NonNull Context context) { - if (AndroidVersion.isAndroid5()) { - AppOpsManager appOps = (AppOpsManager) - context.getSystemService(Context.APP_OPS_SERVICE); - int mode; - if (AndroidVersion.isAndroid10()) { - mode = appOps.unsafeCheckOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, - context.getApplicationInfo().uid, context.getPackageName()); - } else { - mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, - context.getApplicationInfo().uid, context.getPackageName()); - } - return mode == AppOpsManager.MODE_ALLOWED; - } - return true; - } - - /** - * 获取使用统计权限设置界面意图 - */ - private static Intent getPackagePermissionIntent(@NonNull Context context) { - Intent intent = null; - if (AndroidVersion.isAndroid5()) { - intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); - if (AndroidVersion.isAndroid10()) { - // 经过测试,只有在 Android 10 及以上加包名才有效果 - // 如果在 Android 10 以下加包名会导致无法跳转 - intent.setData(PermissionUtils.getPackageNameUri(context)); - } - } - if (intent == null || !PermissionUtils.areActivityIntent(context, intent)) { - intent = PermissionUtils.getApplicationDetailsIntent(context); - } - return intent; - } - /** * 是否有 VPN 权限 */ @@ -231,7 +52,7 @@ private static boolean isGrantedVpnPermission(@NonNull Context context) { */ private static Intent getVpnPermissionIntent(@NonNull Context context) { Intent intent = VpnService.prepare(context); - if (intent == null || !PermissionUtils.areActivityIntent(context, intent)) { + if (!PermissionUtils.areActivityIntent(context, intent)) { intent = PermissionUtils.getApplicationDetailsIntent(context); } return intent; diff --git a/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV18.java b/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV18.java new file mode 100644 index 0000000..e05fa8e --- /dev/null +++ b/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV18.java @@ -0,0 +1,42 @@ +package com.hjq.permissions; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/XXPermissions + * time : 2023/03/11 + * desc : Android 4.3 权限委托实现 + */ +@RequiresApi(api = AndroidVersion.ANDROID_4_3) +class PermissionDelegateImplV18 extends PermissionDelegateImplV14 { + + @Override + public boolean isGrantedPermission(@NonNull Context context, @NonNull String permission) { + // 检测通知栏监听权限 + if (PermissionUtils.equalsPermission(permission, Permission.BIND_NOTIFICATION_LISTENER_SERVICE)) { + return NotificationListenerPermissionCompat.isGrantedPermission(context); + } + return super.isGrantedPermission(context, permission); + } + + @Override + public boolean isPermissionPermanentDenied(@NonNull Activity activity, @NonNull String permission) { + if (PermissionUtils.equalsPermission(permission, Permission.BIND_NOTIFICATION_LISTENER_SERVICE)) { + return false; + } + return super.isPermissionPermanentDenied(activity, permission); + } + + @Override + public Intent getPermissionIntent(@NonNull Context context, @NonNull String permission) { + if (PermissionUtils.equalsPermission(permission, Permission.BIND_NOTIFICATION_LISTENER_SERVICE)) { + return NotificationListenerPermissionCompat.getPermissionIntent(context); + } + return super.getPermissionIntent(context, permission); + } +} diff --git a/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV19.java b/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV19.java new file mode 100644 index 0000000..7ab8664 --- /dev/null +++ b/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV19.java @@ -0,0 +1,90 @@ +package com.hjq.permissions; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/XXPermissions + * time : 2023/03/11 + * desc : Android 4.4 权限委托实现 + */ +@RequiresApi(api = AndroidVersion.ANDROID_4_4) +class PermissionDelegateImplV19 extends PermissionDelegateImplV18 { + + @Override + public boolean isGrantedPermission(@NonNull Context context, @NonNull String permission) { + // 检测悬浮窗权限 + if (PermissionUtils.equalsPermission(permission, Permission.SYSTEM_ALERT_WINDOW)) { + return WindowPermissionCompat.isGrantedPermission(context); + } + + // 检查读取应用列表权限 + if (PermissionUtils.equalsPermission(permission, Permission.GET_INSTALLED_APPS)) { + return GetInstalledAppsPermissionCompat.isGrantedPermission(context); + } + + // 检测通知栏权限 + if (PermissionUtils.equalsPermission(permission, Permission.NOTIFICATION_SERVICE)) { + return NotificationPermissionCompat.isGrantedPermission(context); + } + // 向下兼容 Android 13 新权限 + if (!AndroidVersion.isAndroid13()) { + + if (PermissionUtils.equalsPermission(permission, Permission.POST_NOTIFICATIONS)) { + return NotificationPermissionCompat.isGrantedPermission(context); + } + } + return super.isGrantedPermission(context, permission); + } + + @Override + public boolean isPermissionPermanentDenied(@NonNull Activity activity, @NonNull String permission) { + if (PermissionUtils.equalsPermission(permission, Permission.SYSTEM_ALERT_WINDOW)) { + return false; + } + + if (PermissionUtils.equalsPermission(permission, Permission.GET_INSTALLED_APPS)) { + return GetInstalledAppsPermissionCompat.isPermissionPermanentDenied(activity); + } + + if (PermissionUtils.equalsPermission(permission, Permission.NOTIFICATION_SERVICE)) { + return false; + } + // 向下兼容 Android 13 新权限 + if (!AndroidVersion.isAndroid13()) { + + if (PermissionUtils.equalsPermission(permission, Permission.POST_NOTIFICATIONS)) { + return false; + } + } + return super.isPermissionPermanentDenied(activity, permission); + } + + @Override + public Intent getPermissionIntent(@NonNull Context context, @NonNull String permission) { + if (PermissionUtils.equalsPermission(permission, Permission.SYSTEM_ALERT_WINDOW)) { + return WindowPermissionCompat.getPermissionIntent(context); + } + + if (PermissionUtils.equalsPermission(permission, Permission.GET_INSTALLED_APPS)) { + return GetInstalledAppsPermissionCompat.getPermissionIntent(context); + } + + if (PermissionUtils.equalsPermission(permission, Permission.NOTIFICATION_SERVICE)) { + return NotificationPermissionCompat.getPermissionIntent(context); + } + + // 向下兼容 Android 13 新权限 + if (!AndroidVersion.isAndroid13()) { + + if (PermissionUtils.equalsPermission(permission, Permission.POST_NOTIFICATIONS)) { + return NotificationPermissionCompat.getPermissionIntent(context); + } + } + return super.getPermissionIntent(context, permission); + } +} \ No newline at end of file diff --git a/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV21.java b/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV21.java new file mode 100644 index 0000000..64b658b --- /dev/null +++ b/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV21.java @@ -0,0 +1,67 @@ +package com.hjq.permissions; + +import android.app.Activity; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/XXPermissions + * time : 2022/06/11 + * desc : Android 5.0 权限委托实现 + */ +@RequiresApi(api = AndroidVersion.ANDROID_5) +class PermissionDelegateImplV21 extends PermissionDelegateImplV19 { + + @Override + public boolean isGrantedPermission(@NonNull Context context, @NonNull String permission) { + // 检测获取使用统计权限 + if (PermissionUtils.equalsPermission(permission, Permission.PACKAGE_USAGE_STATS)) { + return isGrantedPackagePermission(context); + } + return super.isGrantedPermission(context, permission); + } + + @Override + public boolean isPermissionPermanentDenied(@NonNull Activity activity, @NonNull String permission) { + if (PermissionUtils.equalsPermission(permission, Permission.PACKAGE_USAGE_STATS)) { + return false; + } + return super.isPermissionPermanentDenied(activity, permission); + } + + @Override + public Intent getPermissionIntent(@NonNull Context context, @NonNull String permission) { + if (PermissionUtils.equalsPermission(permission, Permission.PACKAGE_USAGE_STATS)) { + return getPackagePermissionIntent(context); + } + return super.getPermissionIntent(context, permission); + } + + /** + * 是否有使用统计权限 + */ + private static boolean isGrantedPackagePermission(@NonNull Context context) { + return PermissionUtils.checkOpNoThrow(context, AppOpsManager.OPSTR_GET_USAGE_STATS); + } + + /** + * 获取使用统计权限设置界面意图 + */ + private static Intent getPackagePermissionIntent(@NonNull Context context) { + Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); + if (AndroidVersion.isAndroid10()) { + // 经过测试,只有在 Android 10 及以上加包名才有效果 + // 如果在 Android 10 以下加包名会导致无法跳转 + intent.setData(PermissionUtils.getPackageNameUri(context)); + } + if (!PermissionUtils.areActivityIntent(context, intent)) { + intent = PermissionUtils.getApplicationDetailsIntent(context); + } + return intent; + } +} \ No newline at end of file diff --git a/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV23.java b/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV23.java index c94f5fb..dc3556a 100644 --- a/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV23.java +++ b/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV23.java @@ -4,8 +4,6 @@ import android.app.NotificationManager; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.PermissionInfo; import android.os.PowerManager; import android.provider.Settings; import android.support.annotation.NonNull; @@ -18,47 +16,10 @@ * desc : Android 6.0 权限委托实现 */ @RequiresApi(api = AndroidVersion.ANDROID_6) -class PermissionDelegateImplV23 extends PermissionDelegateImplV14 { +class PermissionDelegateImplV23 extends PermissionDelegateImplV21 { @Override public boolean isGrantedPermission(@NonNull Context context, @NonNull String permission) { - - // 判断是否是特殊权限 - if (PermissionUtils.isSpecialPermission(permission)) { - - // 检测悬浮窗权限 - if (PermissionUtils.equalsPermission(permission, Permission.SYSTEM_ALERT_WINDOW)) { - return isGrantedWindowPermission(context); - } - - // 检测系统权限 - if (PermissionUtils.equalsPermission(permission, Permission.WRITE_SETTINGS)) { - return isGrantedSettingPermission(context); - } - - // 检测勿扰权限 - if (PermissionUtils.equalsPermission(permission, Permission.ACCESS_NOTIFICATION_POLICY)) { - return isGrantedNotDisturbPermission(context); - } - - // 检测电池优化选项权限 - if (PermissionUtils.equalsPermission(permission, Permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) { - return isGrantedIgnoreBatteryPermission(context); - } - - if (!AndroidVersion.isAndroid11()) { - // 检测管理所有文件权限 - if (PermissionUtils.equalsPermission(permission, Permission.MANAGE_EXTERNAL_STORAGE)) { - return PermissionUtils.checkSelfPermission(context, Permission.READ_EXTERNAL_STORAGE) && - PermissionUtils.checkSelfPermission(context, Permission.WRITE_EXTERNAL_STORAGE); - } - } - - return super.isGrantedPermission(context, permission); - } - - /* ---------------------------------------------------------------------------------------- */ - // 向下兼容 Android 13 新权限 if (!AndroidVersion.isAndroid13()) { @@ -82,8 +43,6 @@ public boolean isGrantedPermission(@NonNull Context context, @NonNull String per } } - /* ---------------------------------------------------------------------------------------- */ - // 向下兼容 Android 12 新权限 if (!AndroidVersion.isAndroid12()) { @@ -97,7 +56,15 @@ public boolean isGrantedPermission(@NonNull Context context, @NonNull String per } } - /* ---------------------------------------------------------------------------------------- */ + // 向下兼容 Android 11 新权限 + if (!AndroidVersion.isAndroid11()) { + + // 检测管理所有文件权限 + if (PermissionUtils.equalsPermission(permission, Permission.MANAGE_EXTERNAL_STORAGE)) { + return PermissionUtils.checkSelfPermission(context, Permission.READ_EXTERNAL_STORAGE) && + PermissionUtils.checkSelfPermission(context, Permission.WRITE_EXTERNAL_STORAGE); + } + } // 向下兼容 Android 10 新权限 if (!AndroidVersion.isAndroid10()) { @@ -115,8 +82,6 @@ public boolean isGrantedPermission(@NonNull Context context, @NonNull String per } } - /* ---------------------------------------------------------------------------------------- */ - // 向下兼容 Android 9.0 新权限 if (!AndroidVersion.isAndroid9()) { @@ -125,8 +90,6 @@ public boolean isGrantedPermission(@NonNull Context context, @NonNull String per } } - /* ---------------------------------------------------------------------------------------- */ - // 向下兼容 Android 8.0 新权限 if (!AndroidVersion.isAndroid8()) { @@ -139,32 +102,36 @@ public boolean isGrantedPermission(@NonNull Context context, @NonNull String per } } - /* ---------------------------------------------------------------------------------------- */ + // 交给父类处理 + if (PermissionUtils.equalsPermission(permission, Permission.GET_INSTALLED_APPS) || + PermissionUtils.equalsPermission(permission, Permission.POST_NOTIFICATIONS)) { + return super.isGrantedPermission(context, permission); + } - if (PermissionUtils.equalsPermission(permission, Permission.GET_INSTALLED_APPS)) { - // 判断是否支持申请该权限 - if (isSupportGetInstalledAppsPermission(context)) { - // 如果支持申请,那么再去判断权限是否授予 - return PermissionUtils.checkSelfPermission(context, permission); + if (PermissionUtils.isSpecialPermission(permission)) { + // 检测系统权限 + if (PermissionUtils.equalsPermission(permission, Permission.WRITE_SETTINGS)) { + return isGrantedSettingPermission(context); + } + + // 检测勿扰权限 + if (PermissionUtils.equalsPermission(permission, Permission.ACCESS_NOTIFICATION_POLICY)) { + return isGrantedNotDisturbPermission(context); + } + + // 检测电池优化选项权限 + if (PermissionUtils.equalsPermission(permission, Permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) { + return isGrantedIgnoreBatteryPermission(context); } - // 如果不支持申请,则直接返回 true(代表有这个权限),反正也不会崩溃,顶多就是获取不到第三方应用列表 - return true; - } - /* ---------------------------------------------------------------------------------------- */ + return super.isGrantedPermission(context, permission); + } return PermissionUtils.checkSelfPermission(context, permission); } @Override public boolean isPermissionPermanentDenied(@NonNull Activity activity, @NonNull String permission) { - if (PermissionUtils.isSpecialPermission(permission)) { - // 特殊权限不算,本身申请方式和危险权限申请方式不同,因为没有永久拒绝的选项,所以这里返回 false - return false; - } - - /* ---------------------------------------------------------------------------------------- */ - // 向下兼容 Android 13 新权限 if (!AndroidVersion.isAndroid13()) { @@ -190,8 +157,6 @@ public boolean isPermissionPermanentDenied(@NonNull Activity activity, @NonNull } } - /* ---------------------------------------------------------------------------------------- */ - // 向下兼容 Android 12 新权限 if (!AndroidVersion.isAndroid12()) { @@ -206,8 +171,6 @@ public boolean isPermissionPermanentDenied(@NonNull Activity activity, @NonNull } } - /* ---------------------------------------------------------------------------------------- */ - // 向下兼容 Android 10 新权限 if (!AndroidVersion.isAndroid10()) { @@ -226,8 +189,6 @@ public boolean isPermissionPermanentDenied(@NonNull Activity activity, @NonNull } } - /* ---------------------------------------------------------------------------------------- */ - // 向下兼容 Android 9.0 新权限 if (!AndroidVersion.isAndroid9()) { @@ -236,8 +197,6 @@ public boolean isPermissionPermanentDenied(@NonNull Activity activity, @NonNull } } - /* ---------------------------------------------------------------------------------------- */ - // 向下兼容 Android 8.0 新权限 if (!AndroidVersion.isAndroid8()) { @@ -252,31 +211,23 @@ public boolean isPermissionPermanentDenied(@NonNull Activity activity, @NonNull } } - /* ---------------------------------------------------------------------------------------- */ + // 交给父类处理 + if (PermissionUtils.equalsPermission(permission, Permission.GET_INSTALLED_APPS) || + PermissionUtils.equalsPermission(permission, Permission.POST_NOTIFICATIONS)) { + return super.isPermissionPermanentDenied(activity, permission); + } - if (PermissionUtils.equalsPermission(permission, Permission.GET_INSTALLED_APPS)) { - // 判断是否支持申请该权限 - if (isSupportGetInstalledAppsPermission(activity)) { - // 如果支持申请,那么再去判断权限是否永久拒绝 - return !PermissionUtils.checkSelfPermission(activity, permission) && - !PermissionUtils.shouldShowRequestPermissionRationale(activity, permission); - } - // 如果不支持申请,则直接返回 false(代表没有永久拒绝) + if (PermissionUtils.isSpecialPermission(permission)) { + // 特殊权限不算,本身申请方式和危险权限申请方式不同,因为没有永久拒绝的选项,所以这里返回 false return false; } - /* ---------------------------------------------------------------------------------------- */ - return !PermissionUtils.checkSelfPermission(activity, permission) && !PermissionUtils.shouldShowRequestPermissionRationale(activity, permission); } @Override public Intent getPermissionIntent(@NonNull Context context, @NonNull String permission) { - if (PermissionUtils.equalsPermission(permission, Permission.SYSTEM_ALERT_WINDOW)) { - return getWindowPermissionIntent(context); - } - if (PermissionUtils.equalsPermission(permission, Permission.WRITE_SETTINGS)) { return getSettingPermissionIntent(context); } @@ -292,28 +243,6 @@ public Intent getPermissionIntent(@NonNull Context context, @NonNull String perm return super.getPermissionIntent(context, permission); } - /** - * 是否授予了悬浮窗权限 - */ - private static boolean isGrantedWindowPermission(@NonNull Context context) { - return Settings.canDrawOverlays(context); - } - - /** - * 获取悬浮窗权限设置界面意图 - */ - private static Intent getWindowPermissionIntent(@NonNull Context context) { - Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); - // 在 Android 11 加包名跳转也是没有效果的,官方文档链接: - // https://developer.android.google.cn/reference/android/provider/Settings#ACTION_MANAGE_OVERLAY_PERMISSION - intent.setData(PermissionUtils.getPackageNameUri(context)); - - if (!PermissionUtils.areActivityIntent(context, intent)) { - intent = PermissionUtils.getApplicationDetailsIntent(context); - } - return intent; - } - /** * 是否有系统设置权限 */ @@ -349,13 +278,18 @@ private static boolean isGrantedNotDisturbPermission(@NonNull Context context) { private static Intent getNotDisturbPermissionIntent(@NonNull Context context) { Intent intent = null; - if (AndroidVersion.isAndroid10()) { + // issue 地址:https://github.com/getActivity/XXPermissions/issues/190 + // 这里解释一下,为什么要排除鸿蒙系统,因为用代码能检测到有这个 Intent,也能跳转过去,但是会被马上拒绝 + // 测试过了其他厂商系统及 Android 原生系统都没有这个问题,就只有鸿蒙有这个问题 + // 只因为这个 Intent 是隐藏的意图,所以就不让用,鸿蒙 2.0 和 3.0 都有这个问题 + // 别问鸿蒙 1.0 有没有问题,问就是鸿蒙一发布就 2.0 了,1.0 版本都没有问世过 + if (AndroidVersion.isAndroid10() && !PhoneRomUtils.isHarmonyOs()) { // android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS intent = new Intent("android.settings.NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS"); intent.setData(PermissionUtils.getPackageNameUri(context)); } - if (intent == null || !PermissionUtils.areActivityIntent(context, intent)) { + if (!PermissionUtils.areActivityIntent(context, intent)) { intent = new Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS); } @@ -388,33 +322,4 @@ private static Intent getIgnoreBatteryPermissionIntent(@NonNull Context context) } return intent; } - - /** - * 判断是否支持获取应用列表权限 - */ - private boolean isSupportGetInstalledAppsPermission(Context context) { - try { - PermissionInfo permissionInfo = context.getPackageManager().getPermissionInfo(Permission.GET_INSTALLED_APPS, 0); - if (permissionInfo != null) { - if (AndroidVersion.isAndroid9()) { - return permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS; - } else { - return (permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) == PermissionInfo.PROTECTION_DANGEROUS; - } - } - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - - try { - // 移动终端应用软件列表权限实施指南:http://www.taf.org.cn/upload/AssociationStandard/TTAF%20108-2022%20%E7%A7%BB%E5%8A%A8%E7%BB%88%E7%AB%AF%E5%BA%94%E7%94%A8%E8%BD%AF%E4%BB%B6%E5%88%97%E8%A1%A8%E6%9D%83%E9%99%90%E5%AE%9E%E6%96%BD%E6%8C%87%E5%8D%97.pdf - // 这是兜底方案,因为测试了大量的机型,除了荣耀的 Magic UI 有按照这个规范去做,其他厂商(包括华为的 HarmonyOS)都没有按照这个规范去做 - // 虽然可以只用上面那种判断权限是不是危险权限的方式,但是避免不了有的手机厂商用下面的这种,所以两种都写比较好,小孩子才做选择,大人我全都要 - return Settings.Secure.getInt(context.getContentResolver(), "oem_installed_apps_runtime_permission_enable") == 1; - } catch (Settings.SettingNotFoundException e) { - e.printStackTrace(); - } - - return false; - } } \ No newline at end of file diff --git a/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV26.java b/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV26.java index 41e2f43..af91d18 100644 --- a/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV26.java +++ b/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV26.java @@ -87,16 +87,7 @@ private static Intent getInstallPermissionIntent(@NonNull Context context) { * 是否有画中画权限 */ private static boolean isGrantedPictureInPicturePermission(@NonNull Context context) { - AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - int mode; - if (AndroidVersion.isAndroid10()) { - mode = appOps.unsafeCheckOpNoThrow(AppOpsManager.OPSTR_PICTURE_IN_PICTURE, - context.getApplicationInfo().uid, context.getPackageName()); - } else { - mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_PICTURE_IN_PICTURE, - context.getApplicationInfo().uid, context.getPackageName()); - } - return mode == AppOpsManager.MODE_ALLOWED; + return PermissionUtils.checkOpNoThrow(context, AppOpsManager.OPSTR_PICTURE_IN_PICTURE); } /** diff --git a/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV33.java b/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV33.java index 8fceb98..10aac88 100644 --- a/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV33.java +++ b/library/src/main/java/com/hjq/permissions/PermissionDelegateImplV33.java @@ -30,12 +30,20 @@ public boolean isGrantedPermission(@NonNull Context context, @NonNull String per return PermissionUtils.checkSelfPermission(context, permission); } - // 亲测当这两个条件满足的时候,在 Android 13 不能申请 WRITE_EXTERNAL_STORAGE,会被系统直接拒绝 - // 不会弹出系统授权对话框,框架为了保证不同 Android 版本的回调结果一致性,这里直接返回 true 给到外层 - if (AndroidVersion.getTargetSdkVersionCode(context) >= AndroidVersion.ANDROID_13 && - PermissionUtils.equalsPermission(permission, Permission.WRITE_EXTERNAL_STORAGE)) { - return true; + if (AndroidVersion.getTargetSdkVersionCode(context) >= AndroidVersion.ANDROID_13) { + // 亲测当这两个条件满足的时候,在 Android 13 不能申请 WRITE_EXTERNAL_STORAGE,会被系统直接拒绝 + // 不会弹出系统授权对话框,框架为了保证不同 Android 版本的回调结果一致性,这里直接返回 true 给到外层 + if (PermissionUtils.equalsPermission(permission, Permission.WRITE_EXTERNAL_STORAGE)) { + return true; + } + + if (PermissionUtils.equalsPermission(permission, Permission.READ_EXTERNAL_STORAGE)) { + return PermissionUtils.checkSelfPermission(context, Permission.READ_MEDIA_IMAGES) && + PermissionUtils.checkSelfPermission(context, Permission.READ_MEDIA_VIDEO) && + PermissionUtils.checkSelfPermission(context, Permission.READ_MEDIA_AUDIO); + } } + return super.isGrantedPermission(context, permission); } @@ -58,10 +66,22 @@ public boolean isPermissionPermanentDenied(@NonNull Activity activity, @NonNull !PermissionUtils.shouldShowRequestPermissionRationale(activity, permission); } - if (AndroidVersion.getTargetSdkVersionCode(activity) >= AndroidVersion.ANDROID_13 && - PermissionUtils.equalsPermission(permission, Permission.WRITE_EXTERNAL_STORAGE)) { - return false; + if (AndroidVersion.getTargetSdkVersionCode(activity) >= AndroidVersion.ANDROID_13) { + + if (PermissionUtils.equalsPermission(permission, Permission.WRITE_EXTERNAL_STORAGE)) { + return false; + } + + if (PermissionUtils.equalsPermission(permission, Permission.READ_EXTERNAL_STORAGE)) { + return !PermissionUtils.checkSelfPermission(activity, Permission.READ_MEDIA_IMAGES) && + !PermissionUtils.shouldShowRequestPermissionRationale(activity, Permission.READ_MEDIA_IMAGES) && + !PermissionUtils.checkSelfPermission(activity, Permission.READ_MEDIA_VIDEO) && + !PermissionUtils.shouldShowRequestPermissionRationale(activity, Permission.READ_MEDIA_VIDEO) && + !PermissionUtils.checkSelfPermission(activity, Permission.READ_MEDIA_AUDIO) && + !PermissionUtils.shouldShowRequestPermissionRationale(activity, Permission.READ_MEDIA_AUDIO); + } } + return super.isPermissionPermanentDenied(activity, permission); } } \ No newline at end of file diff --git a/library/src/main/java/com/hjq/permissions/PermissionFragment.java b/library/src/main/java/com/hjq/permissions/PermissionFragment.java index 0152739..2f78fd8 100644 --- a/library/src/main/java/com/hjq/permissions/PermissionFragment.java +++ b/library/src/main/java/com/hjq/permissions/PermissionFragment.java @@ -207,7 +207,7 @@ public void requestSpecialPermission() { continue; } // 跳转到特殊权限授权页面 - startActivityForResult(PermissionUtils.getSmartPermissionIntent(activity, + StartActivityManager.startActivityForResult(this, PermissionUtils.getSmartPermissionIntent(activity, PermissionUtils.asArrayList(permission)), getArguments().getInt(REQUEST_CODE)); requestSpecialPermission = true; } diff --git a/library/src/main/java/com/hjq/permissions/PermissionIntentManager.java b/library/src/main/java/com/hjq/permissions/PermissionIntentManager.java new file mode 100644 index 0000000..8f05fc3 --- /dev/null +++ b/library/src/main/java/com/hjq/permissions/PermissionIntentManager.java @@ -0,0 +1,285 @@ +package com.hjq.permissions; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.provider.Settings; +import android.support.annotation.Nullable; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/XXPermissions + * time : 2023/03/12 + * desc : 国内手机厂商权限设置页管理器 + */ +final class PermissionIntentManager { + + /** 华为手机管家 App 包名 */ + private static final String EMUI_MOBILE_MANAGER_APP_PACKAGE_NAME = "com.huawei.systemmanager"; + + /** 小米手机管家 App 包名 */ + private static final String MIUI_MOBILE_MANAGER_APP_PACKAGE_NAME = "com.miui.securitycenter"; + + /** OPPO 安全中心 App 包名 */ + private static final String COLOR_OS_SAFE_CENTER_APP_PACKAGE_NAME_1 = "com.oppo.safe"; + private static final String COLOR_OS_SAFE_CENTER_APP_PACKAGE_NAME_2 = "com.color.safecenter"; + private static final String COLOR_OS_SAFE_CENTER_APP_PACKAGE_NAME_3 = "com.oplus.safecenter"; + + /** vivo 安全中心 App 包名 */ + private static final String ORIGIN_OS_MOBILE_MANAGER_APP_PACKAGE_NAME = "com.iqoo.secure"; + + /** + * 获取华为悬浮窗权限设置意图 + */ + @Nullable + static Intent getEmuiWindowPermissionPageIntent(Context context) { + // EMUI 发展史:http://www.360doc.com/content/19/1017/10/9113704_867381705.shtml + // android 华为版本历史,一文看完华为EMUI发展史:https://blog.csdn.net/weixin_39959369/article/details/117351161 + + Intent addViewMonitorActivityIntent = new Intent(); + // emui 3.1 的适配(华为荣耀 7 Android 5.0、华为揽阅 M2 青春版 Android 5.1、华为畅享 5S Android 5.1) + addViewMonitorActivityIntent.setClassName(EMUI_MOBILE_MANAGER_APP_PACKAGE_NAME, EMUI_MOBILE_MANAGER_APP_PACKAGE_NAME + ".addviewmonitor.AddViewMonitorActivity"); + + Intent notificationManagementActivityIntent = new Intent(); + // emui 3.0 的适配(华为麦芒 3S Android 4.4) + notificationManagementActivityIntent.setClassName(EMUI_MOBILE_MANAGER_APP_PACKAGE_NAME, "com.huawei.notificationmanager.ui.NotificationManagmentActivity"); + + // 华为手机管家主页 + Intent huaWeiMobileManagerAppIntent = getHuaWeiMobileManagerAppIntent(context); + + // 获取厂商版本号 + String romVersionName = PhoneRomUtils.getRomVersionName(); + if (romVersionName == null) { + romVersionName = ""; + } + + Intent intent = null; + if (romVersionName.startsWith("3.0")) { + // 3.0、3.0.1 + if (PermissionUtils.areActivityIntent(context, notificationManagementActivityIntent)) { + intent = notificationManagementActivityIntent; + } + + if (PermissionUtils.areActivityIntent(context, addViewMonitorActivityIntent)) { + intent = StartActivityManager.addSubIntentToMainIntent(intent, addViewMonitorActivityIntent); + } + } else { + // 3.1、其他的 + if (PermissionUtils.areActivityIntent(context, addViewMonitorActivityIntent)) { + intent = addViewMonitorActivityIntent; + } + + if (PermissionUtils.areActivityIntent(context, notificationManagementActivityIntent)) { + intent = StartActivityManager.addSubIntentToMainIntent(intent, notificationManagementActivityIntent); + } + } + + if (PermissionUtils.areActivityIntent(context, huaWeiMobileManagerAppIntent)) { + intent = StartActivityManager.addSubIntentToMainIntent(intent, huaWeiMobileManagerAppIntent); + } + + return intent; + } + + /** + * 获取小米悬浮窗权限设置意图 + */ + @Nullable + static Intent getMiuiWindowPermissionPageIntent(Context context) { + return getMiuiPermissionPageIntent(context); + } + + /** + * 获取 oppo 悬浮窗权限设置意图 + */ + @Nullable + static Intent getColorOsWindowPermissionPageIntent(Context context) { + // com.color.safecenter 是之前 oppo 安全中心的包名,而 com.oppo.safe 是 oppo 后面改的安全中心的包名 + // 经过测试发现是在 ColorOs 2.1 的时候改的,Android 4.4 还是 com.color.safecenter,到了 Android 5.0 变成了 com.oppo.safe + + // java.lang.SecurityException: Permission Denial: starting Intent + // { cmp=com.oppo.safe/.permission.floatwindow.FloatWindowListActivity (has extras) } from + // ProcessRecord{839a7c5 10595:com.hjq.permissions.demo/u0a3781} (pid=10595, uid=13781) not exported from uid 1000 + // intent.setClassName("com.oppo.safe", "com.oppo.safe.permission.floatwindow.FloatWindowListActivity"); + + // java.lang.SecurityException: Permission Denial: starting Intent + // { cmp=com.color.safecenter/.permission.floatwindow.FloatWindowListActivity (has extras) } from + // ProcessRecord{42b660b0 31279:com.hjq.permissions.demo/u0a204} (pid=31279, uid=10204) not exported from uid 1000 + // intent.setClassName("com.color.safecenter", "com.color.safecenter.permission.floatwindow.FloatWindowListActivity"); + + // java.lang.SecurityException: Permission Denial: starting Intent + // { cmp=com.color.safecenter/.permission.PermissionAppAllPermissionActivity (has extras) } from + // ProcessRecord{42c49dd8 1791:com.hjq.permissions.demo/u0a204} (pid=1791, uid=10204) not exported from uid 1000 + // intent.setClassName("com.color.safecenter", "com.color.safecenter.permission.PermissionAppAllPermissionActivity"); + + // 虽然不能直接到达悬浮窗界面,但是到达它的上一级页面(权限隐私页面)还是可以的,所以做了简单的取舍 + // 测试机是 OPPO R7 Plus(Android 5.0,ColorOs 2.1)、OPPO R7s(Android 4.4,ColorOs 2.1) + // com.oppo.safe.permission.PermissionTopActivity + // com.oppo.safe..permission.PermissionAppListActivity + // com.color.safecenter.permission.PermissionTopActivity + Intent permissionTopActivityActionIntent = new Intent("com.oppo.safe.permission.PermissionTopActivity"); + + Intent oppoSafeCenterAppIntent = getOppoSafeCenterAppIntent(context); + + Intent intent = null; + + if (PermissionUtils.areActivityIntent(context, permissionTopActivityActionIntent)) { + intent = permissionTopActivityActionIntent; + } + + if (PermissionUtils.areActivityIntent(context, oppoSafeCenterAppIntent)) { + intent = StartActivityManager.addSubIntentToMainIntent(intent, oppoSafeCenterAppIntent);; + } + + return intent; + } + + /** + * 获取 vivo 悬浮窗权限设置意图 + */ + @Nullable + static Intent getOriginOsWindowPermissionPageIntent(Context context) { + // java.lang.SecurityException: Permission Denial: starting Intent + // { cmp=com.iqoo.secure/.ui.phoneoptimize.FloatWindowManager (has extras) } from + // ProcessRecord{2c3023cf 21847:com.hjq.permissions.demo/u0a4633} (pid=21847, uid=14633) not exported from uid 10055 + // intent.setClassName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.FloatWindowManager"); + + // java.lang.SecurityException: Permission Denial: starting Intent + // { cmp=com.iqoo.secure/.safeguard.PurviewTabActivity (has extras) } from + // ProcessRecord{2c3023cf 21847:com.hjq.permissions.demo/u0a4633} (pid=21847, uid=14633) not exported from uid 10055 + // intent.setClassName("com.iqoo.secure", "com.iqoo.secure.safeguard.PurviewTabActivity"); + + // 经过测试在 vivo x7 Plus(Android 5.1)上面能跳转过去,但是显示却是一个空白页面 + // intent.setClassName("com.iqoo.secure", "com.iqoo.secure.safeguard.SoftPermissionDetailActivity"); + + Intent intent = getVivoMobileManagerAppIntent(context); + if (PermissionUtils.areActivityIntent(context, intent)) { + return intent; + } + + return null; + } + + @Nullable + static Intent getOneUiWindowPermissionPageIntent(Context context) { + return getOneUiPermissionPageIntent(context); + } + + /* ---------------------------------------------------------------------------------------- */ + + @Nullable + static Intent getMiuiPermissionPageIntent(Context context) { + Intent appPermEditorActionIntent = new Intent() + .setAction("miui.intent.action.APP_PERM_EDITOR") + .putExtra("extra_pkgname", context.getPackageName()); + + Intent xiaoMiMobileManagerAppIntent = getXiaoMiMobileManagerAppIntent(context); + + Intent intent = null; + if (PermissionUtils.areActivityIntent(context, appPermEditorActionIntent)) { + intent = appPermEditorActionIntent; + } + + if (PermissionUtils.areActivityIntent(context, xiaoMiMobileManagerAppIntent)) { + intent = StartActivityManager.addSubIntentToMainIntent(intent, xiaoMiMobileManagerAppIntent); + } + + return intent; + } + + @Nullable + static Intent getOriginOsPermissionPageIntent(Context context) { + // vivo iQOO 9 Pro(OriginOs 2.0 Android 12) + Intent intent = new Intent("permission.intent.action.softPermissionDetail"); + intent.putExtra("packagename", context.getPackageName()); + if (PermissionUtils.areActivityIntent(context, intent)) { + return intent; + } + return null; + } + + /** + * 获取三星权限设置意图 + */ + @Nullable + static Intent getOneUiPermissionPageIntent(Context context) { + Intent intent = new Intent(); + intent.setClassName("com.android.settings", "com.android.settings.Settings$AppOpsDetailsActivity"); + Bundle extraShowFragmentArguments = new Bundle(); + extraShowFragmentArguments.putString("package", context.getPackageName()); + intent.putExtra(":settings:show_fragment_args", extraShowFragmentArguments); + intent.setData(PermissionUtils.getPackageNameUri(context)); + if (PermissionUtils.areActivityIntent(context, intent)) { + return intent; + } + return null; + } + + /* ---------------------------------------------------------------------------------------- */ + + /** + * 返回华为手机管家 App 意图 + */ + @Nullable + static Intent getHuaWeiMobileManagerAppIntent(Context context) { + Intent intent = context.getPackageManager().getLaunchIntentForPackage(EMUI_MOBILE_MANAGER_APP_PACKAGE_NAME); + if (PermissionUtils.areActivityIntent(context, intent)) { + return intent; + } + return null; + } + + /** + * 返回小米手机管家 App 意图 + */ + @Nullable + static Intent getXiaoMiMobileManagerAppIntent(Context context) { + Intent intent = context.getPackageManager().getLaunchIntentForPackage(MIUI_MOBILE_MANAGER_APP_PACKAGE_NAME); + if (PermissionUtils.areActivityIntent(context, intent)) { + return intent; + } + return null; + } + + /** + * 获取 oppo 安全中心 App 意图 + */ + @Nullable + static Intent getOppoSafeCenterAppIntent(Context context) { + Intent intent = context.getPackageManager().getLaunchIntentForPackage(COLOR_OS_SAFE_CENTER_APP_PACKAGE_NAME_1); + if (PermissionUtils.areActivityIntent(context, intent)) { + return intent; + } + intent = context.getPackageManager().getLaunchIntentForPackage(COLOR_OS_SAFE_CENTER_APP_PACKAGE_NAME_2); + if (PermissionUtils.areActivityIntent(context, intent)) { + return intent; + } + intent = context.getPackageManager().getLaunchIntentForPackage(COLOR_OS_SAFE_CENTER_APP_PACKAGE_NAME_3); + if (PermissionUtils.areActivityIntent(context, intent)) { + return intent; + } + return null; + } + + /** + * 获取 vivo 管家手机意图 + */ + @Nullable + static Intent getVivoMobileManagerAppIntent(Context context) { + Intent intent = context.getPackageManager().getLaunchIntentForPackage(ORIGIN_OS_MOBILE_MANAGER_APP_PACKAGE_NAME); + if (PermissionUtils.areActivityIntent(context, intent)) { + return intent; + } + return null; + } + + /** 跳转到系统设置页面 */ + @Nullable + static Intent getAndroidSettingAppIntent(Context context) { + Intent intent = new Intent(Settings.ACTION_SETTINGS); + if (PermissionUtils.areActivityIntent(context, intent)) { + return intent; + } + return null; + } +} \ No newline at end of file diff --git a/library/src/main/java/com/hjq/permissions/PermissionPageFragment.java b/library/src/main/java/com/hjq/permissions/PermissionPageFragment.java index 1ba95ba..48cda05 100644 --- a/library/src/main/java/com/hjq/permissions/PermissionPageFragment.java +++ b/library/src/main/java/com/hjq/permissions/PermissionPageFragment.java @@ -101,7 +101,7 @@ public void onResume() { return; } List permissions = arguments.getStringArrayList(REQUEST_PERMISSIONS); - startActivityForResult(PermissionUtils.getSmartPermissionIntent(getActivity(), permissions), XXPermissions.REQUEST_CODE); + StartActivityManager.startActivityForResult(this, PermissionUtils.getSmartPermissionIntent(getActivity(), permissions), XXPermissions.REQUEST_CODE); } @Override diff --git a/library/src/main/java/com/hjq/permissions/PermissionUtils.java b/library/src/main/java/com/hjq/permissions/PermissionUtils.java index b53cef9..b4173e6 100644 --- a/library/src/main/java/com/hjq/permissions/PermissionUtils.java +++ b/library/src/main/java/com/hjq/permissions/PermissionUtils.java @@ -2,6 +2,7 @@ import android.annotation.SuppressLint; import android.app.Activity; +import android.app.AppOpsManager; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; @@ -11,7 +12,6 @@ import android.content.res.AssetManager; import android.content.res.Configuration; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -25,6 +25,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -45,7 +46,7 @@ final class PermissionUtils { /** * 判断某个权限是否是特殊权限 */ - public static boolean isSpecialPermission(@NonNull String permission) { + static boolean isSpecialPermission(@NonNull String permission) { return equalsPermission(permission, Permission.MANAGE_EXTERNAL_STORAGE) || equalsPermission(permission, Permission.REQUEST_INSTALL_PACKAGES) || equalsPermission(permission, Permission.SYSTEM_ALERT_WINDOW) || @@ -64,10 +65,52 @@ public static boolean isSpecialPermission(@NonNull String permission) { * 判断某个危险权限是否授予了 */ @RequiresApi(api = AndroidVersion.ANDROID_6) - public static boolean checkSelfPermission(@NonNull Context context, @NonNull String permission) { + static boolean checkSelfPermission(@NonNull Context context, @NonNull String permission) { return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; } + @SuppressWarnings("ConstantConditions") + @RequiresApi(AndroidVersion.ANDROID_4_4) + static boolean checkOpNoThrow(Context context, String opFieldName, int opDefaultValue) { + AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + ApplicationInfo appInfo = context.getApplicationInfo(); + String pkg = context.getApplicationContext().getPackageName(); + int uid = appInfo.uid; + try { + Class appOpsClass = Class.forName(AppOpsManager.class.getName()); + int opValue; + try { + Field opValueField = appOpsClass.getDeclaredField(opFieldName); + opValue = (int) opValueField.get(Integer.class); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + opValue = opDefaultValue; + } + Method checkOpNoThrowMethod = appOpsClass.getMethod("checkOpNoThrow", Integer.TYPE, + Integer.TYPE, String.class); + return ((int) checkOpNoThrowMethod.invoke(appOps, opValue, uid, pkg) + == AppOpsManager.MODE_ALLOWED); + } catch (ClassNotFoundException | NoSuchMethodException | + InvocationTargetException | IllegalAccessException | RuntimeException e) { + return true; + } + } + + @RequiresApi(AndroidVersion.ANDROID_4_4) + static boolean checkOpNoThrow(Context context, String opName) { + AppOpsManager appOps = (AppOpsManager) + context.getSystemService(Context.APP_OPS_SERVICE); + int mode; + if (AndroidVersion.isAndroid10()) { + mode = appOps.unsafeCheckOpNoThrow(opName, + context.getApplicationInfo().uid, context.getPackageName()); + } else { + mode = appOps.checkOpNoThrow(opName, + context.getApplicationInfo().uid, context.getPackageName()); + } + return mode == AppOpsManager.MODE_ALLOWED; + } + /** * 解决 Android 12 调用 shouldShowRequestPermissionRationale 出现内存泄漏的问题 * Android 12L 和 Android 13 版本经过测试不会出现这个问题,证明 Google 在新版本上已经修复了这个问题 @@ -77,7 +120,7 @@ public static boolean checkSelfPermission(@NonNull Context context, @NonNull Str */ @RequiresApi(api = AndroidVersion.ANDROID_6) @SuppressWarnings({"JavaReflectionMemberAccess", "ConstantConditions", "BooleanMethodIsAlwaysInverted"}) - public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity, @NonNull String permission) { + static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity, @NonNull String permission) { if (AndroidVersion.getAndroidVersionCode() == AndroidVersion.ANDROID_12) { try { PackageManager packageManager = activity.getApplication().getPackageManager(); @@ -90,13 +133,6 @@ public static boolean shouldShowRequestPermissionRationale(@NonNull Activity act return activity.shouldShowRequestPermissionRationale(permission); } - /** - * 延迟一段时间执行 - */ - static void postDelayed(@NonNull Runnable runnable, long delayMillis) { - HANDLER.postDelayed(runnable, delayMillis); - } - /** * 延迟一段时间执行 OnActivityResult,避免有些机型明明授权了,但还是回调失败的问题 */ @@ -108,15 +144,14 @@ static void postActivityResult(@NonNull List permissions, @NonNull Runna delayMillis = 300; } - String manufacturer = Build.MANUFACTURER.toLowerCase(); - if (manufacturer.contains("huawei")) { + if (PhoneRomUtils.isEmui() || PhoneRomUtils.isHarmonyOs()) { // 需要加长时间等待,不然某些华为机型授权了但是获取不到权限 if (AndroidVersion.isAndroid8()) { delayMillis = 300; } else { delayMillis = 500; } - } else if (manufacturer.contains("xiaomi")) { + } else if (PhoneRomUtils.isMiui()) { // 经过测试,发现小米 Android 11 及以上的版本,申请这个权限需要 1 秒钟才能判断到 // 因为在 Android 10 的时候,这个特殊权限弹出的页面小米还是用谷歌原生的 // 然而在 Android 11 之后的,这个权限页面被小米改成了自己定制化的页面 @@ -126,7 +161,13 @@ static void postActivityResult(@NonNull List permissions, @NonNull Runna delayMillis = 1000; } } + postDelayed(runnable, delayMillis); + } + /** + * 延迟一段时间执行 + */ + static void postDelayed(@NonNull Runnable runnable, long delayMillis) { HANDLER.postDelayed(runnable, delayMillis); } @@ -416,30 +457,44 @@ static boolean isActivityReverse(@NonNull Activity activity) { * 判断这个意图的 Activity 是否存在 */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") - static boolean areActivityIntent(@NonNull Context context, @NonNull Intent intent) { - return intent.resolveActivity(context.getPackageManager()) != null; + static boolean areActivityIntent(@NonNull Context context, @Nullable Intent intent) { + if (intent == null) { + return false; + } + // 这里为什么不用 Intent.resolveActivity(intent) != null 来判断呢? + // 这是因为在 OPPO R7 Plus (Android 5.0)会出现误判,明明没有这个 Activity,却返回了 ComponentName 对象 + PackageManager packageManager = context.getPackageManager(); + if (AndroidVersion.isAndroid13()) { + return !packageManager.queryIntentActivities(intent, + PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY)).isEmpty(); + } + return !packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).isEmpty(); } /** * 获取应用详情界面意图 */ - public static Intent getApplicationDetailsIntent(@NonNull Context context) { + static Intent getApplicationDetailsIntent(@NonNull Context context) { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(getPackageNameUri(context)); - if (!PermissionUtils.areActivityIntent(context, intent)) { - intent = new Intent(Settings.ACTION_APPLICATION_SETTINGS); - if (!PermissionUtils.areActivityIntent(context, intent)) { - intent = new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS); - } + + if (PermissionUtils.areActivityIntent(context, intent)) { + return intent; + } + + intent = new Intent(Settings.ACTION_APPLICATION_SETTINGS); + if (PermissionUtils.areActivityIntent(context, intent)) { + return intent; } + intent = new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS); return intent; } /** * 获取包名 uri */ - public static Uri getPackageNameUri(@NonNull Context context) { + static Uri getPackageNameUri(@NonNull Context context) { return Uri.parse("package:" + context.getPackageName()); } @@ -450,11 +505,19 @@ public static Uri getPackageNameUri(@NonNull Context context) { */ static Intent getSmartPermissionIntent(@NonNull Context context, @Nullable List permissions) { // 如果失败的权限里面不包含特殊权限 - if (permissions == null || permissions.isEmpty() || - !PermissionApi.containsSpecialPermission(permissions)) { + if (permissions == null || permissions.isEmpty()) { + return getApplicationDetailsIntent(context); + } + + // 危险权限统一处理 + if (!PermissionApi.containsSpecialPermission(permissions)) { + if (permissions.size() == 1) { + return PermissionApi.getPermissionIntent(context, permissions.get(0)); + } return getApplicationDetailsIntent(context); } + // 特殊权限统一处理 switch (permissions.size()) { case 1: // 如果当前只有一个权限被拒绝了 diff --git a/library/src/main/java/com/hjq/permissions/PhoneRomUtils.java b/library/src/main/java/com/hjq/permissions/PhoneRomUtils.java new file mode 100644 index 0000000..ecfa7ff --- /dev/null +++ b/library/src/main/java/com/hjq/permissions/PhoneRomUtils.java @@ -0,0 +1,302 @@ +package com.hjq.permissions; + +import android.annotation.SuppressLint; +import android.os.Build; +import android.os.Environment; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Properties; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/XXPermissions + * time : 2023/04/05 + * desc : 厂商 Rom 工具类 + */ +final class PhoneRomUtils { + + private static final String[] ROM_HUAWEI = {"huawei"}; + private static final String[] ROM_VIVO = {"vivo"}; + private static final String[] ROM_XIAOMI = {"xiaomi"}; + private static final String[] ROM_OPPO = {"oppo"}; + private static final String[] ROM_LEECO = {"leeco", "letv"}; + private static final String[] ROM_360 = {"360", "qiku"}; + private static final String[] ROM_ZTE = {"zte"}; + private static final String[] ROM_ONEPLUS = {"oneplus"}; + private static final String[] ROM_NUBIA = {"nubia"}; + private static final String[] ROM_SAMSUNG = {"samsung"}; + + private static final String ROM_NAME_MIUI = "ro.miui.ui.version.name"; + + private static final String VERSION_PROPERTY_HUAWEI = "ro.build.version.emui"; + private static final String VERSION_PROPERTY_VIVO = "ro.vivo.os.build.display.id"; + private static final String VERSION_PROPERTY_XIAOMI = "ro.build.version.incremental"; + private static final String[] VERSION_PROPERTY_OPPO = {"ro.build.version.opporom", "ro.build.version.oplusrom.display"}; + private static final String VERSION_PROPERTY_LEECO = "ro.letv.release.version"; + private static final String VERSION_PROPERTY_360 = "ro.build.uiversion"; + private static final String VERSION_PROPERTY_ZTE = "ro.build.MiFavor_version"; + private static final String VERSION_PROPERTY_ONEPLUS = "ro.rom.version"; + private static final String VERSION_PROPERTY_NUBIA = "ro.build.rom.id"; + + private PhoneRomUtils() {} + + /** + * 判断当前厂商系统是否为 emui + */ + static boolean isEmui() { + return !TextUtils.isEmpty(getPropertyName(VERSION_PROPERTY_HUAWEI)); + } + + /** + * 判断当前厂商系统是否为 miui + */ + static boolean isMiui() { + return !TextUtils.isEmpty(getPropertyName(ROM_NAME_MIUI)); + } + + /** + * 判断当前厂商系统是否为 ColorOs + */ + static boolean isColorOs() { + for (String property : VERSION_PROPERTY_OPPO) { + String versionName = getPropertyName(property); + if (TextUtils.isEmpty(versionName)) { + continue; + } + return true; + } + return false; + } + + /** + * 判断当前厂商系统是否为 OriginOS + */ + static boolean isOriginOs() { + return !TextUtils.isEmpty(getPropertyName(VERSION_PROPERTY_VIVO)); + } + + /** + * 判断当前厂商系统是否为 OneUI + */ + @SuppressLint("PrivateApi") + static boolean isOneUi() { + return isRightRom(getBrand(), getManufacturer(), ROM_SAMSUNG); + // 暂时无法通过下面的方式判断是否为 OneUI,只能通过品牌和机型来判断 + // https://stackoverflow.com/questions/60122037/how-can-i-detect-samsung-one-ui +// try { +// Field semPlatformIntField = Build.VERSION.class.getDeclaredField("SEM_PLATFORM_INT"); +// semPlatformIntField.setAccessible(true); +// int semPlatformVersion = semPlatformIntField.getInt(null); +// return semPlatformVersion >= 100000; +// } catch (NoSuchFieldException e) { +// e.printStackTrace(); +// return false; +// } catch (IllegalAccessException e) { +// e.printStackTrace(); +// return false; +// } + } + + /** + * 判断当前是否为鸿蒙系统 + */ + static boolean isHarmonyOs() { + try { + Class buildExClass = Class.forName("com.huawei.system.BuildEx"); + Object osBrand = buildExClass.getMethod("getOsBrand").invoke(buildExClass); + return "Harmony".equalsIgnoreCase(String.valueOf(osBrand)); + } catch (Throwable throwable) { + throwable.printStackTrace(); + return false; + } + } + + /** + * 判断 miui 优化开关(默认开启,关闭步骤为:开发者选项-> 启动 MIUI 优化 -> 点击关闭) + * 需要注意的是,关闭 miui 优化后,可以跳转到小米定制的权限请求页面,但是开启权限仍然是没有效果的 + * 另外关于 miui 国际版开发者选项中是没有 miui 优化选项的,但是代码判断是有开启 miui 优化,也就是默认开启,这样是正确的 + * 相关 Github issue 地址:https://github.com/getActivity/XXPermissions/issues/38 + */ + @SuppressLint("PrivateApi") + static boolean isMiuiOptimization() { + try { + Class clazz = Class.forName("android.os.SystemProperties"); + Method getMethod = clazz.getMethod("get", String.class, String.class); + String ctsValue = String.valueOf(getMethod.invoke(clazz, "ro.miui.cts", "")); + Method getBooleanMethod = clazz.getMethod("getBoolean", String.class, boolean.class); + return Boolean.parseBoolean(String.valueOf(getBooleanMethod.invoke(clazz, "persist.sys.miui_optimization", !"1".equals(ctsValue)))); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return true; + } + + /** + * 返回厂商系统版本号 + */ + @Nullable + static String getRomVersionName() { + final String brand = getBrand(); + final String manufacturer = getManufacturer(); + if (isRightRom(brand, manufacturer, ROM_HUAWEI)) { + String version = getPropertyName(VERSION_PROPERTY_HUAWEI); + String[] temp = version.split("_"); + if (temp.length > 1) { + return temp[1]; + } else { + // 需要注意的是 华为畅享 5S Android 5.1 获取到的厂商版本号是 EmotionUI 3,而不是 3.1 或者 3.0 这种 + if (version.contains("EmotionUI")) { + return version.replaceFirst("EmotionUI\\s*", ""); + } + return version; + } + } + if (isRightRom(brand, manufacturer, ROM_VIVO)) { + // 需要注意的是 vivo iQOO 9 Pro Android 12 获取到的厂商版本号是 OriginOS Ocean + return getPropertyName(VERSION_PROPERTY_VIVO); + } + if (isRightRom(brand, manufacturer, ROM_XIAOMI)) { + return getPropertyName(VERSION_PROPERTY_XIAOMI); + } + if (isRightRom(brand, manufacturer, ROM_OPPO)) { + for (String property : VERSION_PROPERTY_OPPO) { + String versionName = getPropertyName(property); + if (TextUtils.isEmpty(property)) { + continue; + } + return versionName; + } + return ""; + } + if (isRightRom(brand, manufacturer, ROM_LEECO)) { + return getPropertyName(VERSION_PROPERTY_LEECO); + } + + if (isRightRom(brand, manufacturer, ROM_360)) { + return getPropertyName(VERSION_PROPERTY_360); + } + if (isRightRom(brand, manufacturer, ROM_ZTE)) { + return getPropertyName(VERSION_PROPERTY_ZTE); + } + if (isRightRom(brand, manufacturer, ROM_ONEPLUS)) { + return getPropertyName(VERSION_PROPERTY_ONEPLUS); + } + if (isRightRom(brand, manufacturer, ROM_NUBIA)) { + return getPropertyName(VERSION_PROPERTY_NUBIA); + } + + return getPropertyName(""); + } + + private static boolean isRightRom(final String brand, final String manufacturer, final String... names) { + for (String name : names) { + if (brand.contains(name) || manufacturer.contains(name)) { + return true; + } + } + return false; + } + + private static String getBrand() { + return Build.BRAND.toLowerCase(); + } + + private static String getManufacturer() { + return Build.MANUFACTURER.toLowerCase(); + } + + private static String getPropertyName(final String propertyName) { + String result = ""; + if (!TextUtils.isEmpty(propertyName)) { + result = getSystemProperty(propertyName); + } + return result; + } + + private static String getSystemProperty(final String name) { + String prop = getSystemPropertyByShell(name); + if (!TextUtils.isEmpty(prop)) { + return prop; + } + prop = getSystemPropertyByStream(name); + if (!TextUtils.isEmpty(prop)) { + return prop; + } + if (Build.VERSION.SDK_INT < 28) { + return getSystemPropertyByReflect(name); + } + return prop; + } + + private static String getSystemPropertyByShell(final String propName) { + BufferedReader input = null; + try { + Process p = Runtime.getRuntime().exec("getprop " + propName); + input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); + String ret = input.readLine(); + if (ret != null) { + return ret; + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (input != null) { + try { + input.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return ""; + } + + private static String getSystemPropertyByStream(final String key) { + try { + Properties prop = new Properties(); + FileInputStream is = new FileInputStream( + new File(Environment.getRootDirectory(), "build.prop") + ); + prop.load(is); + return prop.getProperty(key, ""); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return ""; + } + + @SuppressLint("PrivateApi") + private static String getSystemPropertyByReflect(String key) { + try { + Class clz = Class.forName("android.os.SystemProperties"); + Method getMethod = clz.getMethod("get", String.class, String.class); + return (String) getMethod.invoke(clz, key, ""); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return ""; + } +} \ No newline at end of file diff --git a/library/src/main/java/com/hjq/permissions/StartActivityManager.java b/library/src/main/java/com/hjq/permissions/StartActivityManager.java new file mode 100644 index 0000000..4022183 --- /dev/null +++ b/library/src/main/java/com/hjq/permissions/StartActivityManager.java @@ -0,0 +1,193 @@ +package com.hjq.permissions; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/XXPermissions + * time : 2023/04/05 + * desc : startActivity 管理器 + */ +final class StartActivityManager { + + private static final String SUB_INTENT_KEY = "sub_intent_key"; + + static Intent getSubIntentInMainIntent(@NonNull Intent mainIntent) { + Intent subIntent; + if (AndroidVersion.isAndroid13()) { + subIntent = mainIntent.getParcelableExtra(SUB_INTENT_KEY, Intent.class); + } else { + subIntent = mainIntent.getParcelableExtra(SUB_INTENT_KEY); + } + return subIntent; + } + + static Intent getDeepSubIntent(@NonNull Intent superIntent) { + Intent subIntent = getSubIntentInMainIntent(superIntent); + if (subIntent != null) { + return getDeepSubIntent(subIntent); + } + return superIntent; + } + + static Intent addSubIntentToMainIntent(@Nullable Intent mainIntent, @Nullable Intent subIntent) { + if (mainIntent == null && subIntent != null) { + return subIntent; + } + if (subIntent == null) { + return mainIntent; + } + Intent deepSubIntent = getDeepSubIntent(mainIntent); + deepSubIntent.putExtra(SUB_INTENT_KEY, subIntent); + return mainIntent; + } + + static boolean startActivity(@NonNull Context context, Intent intent) { + return startActivity(new StartActivityDelegateContextImpl(context), intent); + } + + static boolean startActivity(@NonNull Activity activity, Intent intent) { + return startActivity(new StartActivityDelegateActivityImpl(activity), intent); + } + + static boolean startActivity(@NonNull Fragment fragment, Intent intent) { + return startActivity(new StartActivityDelegateFragmentImpl(fragment), intent); + } + + static boolean startActivity(@NonNull android.support.v4.app.Fragment fragment, Intent intent) { + return startActivity(new StartActivityDelegateSupportFragmentImpl(fragment), intent); + } + + static boolean startActivity(@NonNull IStartActivityDelegate delegate, @NonNull Intent intent) { + try { + delegate.startActivity(intent); + return true; + } catch (Exception e) { + e.printStackTrace(); + Intent subIntent = getSubIntentInMainIntent(intent); + if (subIntent == null) { + return false; + } + return startActivity(delegate, subIntent); + } + } + + static boolean startActivityForResult(@NonNull Activity activity, @NonNull Intent intent, int requestCode) { + return startActivityForResult(new StartActivityDelegateActivityImpl(activity), intent, requestCode); + } + + static boolean startActivityForResult(@NonNull Fragment fragment, @NonNull Intent intent, int requestCode) { + return startActivityForResult(new StartActivityDelegateFragmentImpl(fragment), intent, requestCode); + } + + static boolean startActivityForResult(@NonNull android.support.v4.app.Fragment fragment, @NonNull Intent intent, int requestCode) { + return startActivityForResult(new StartActivityDelegateSupportFragmentImpl(fragment), intent, requestCode); + } + + static boolean startActivityForResult(@NonNull IStartActivityDelegate delegate, @NonNull Intent intent, int requestCode) { + try { + delegate.startActivityForResult(intent, requestCode); + return true; + } catch (Exception e) { + e.printStackTrace(); + Intent subIntent = getSubIntentInMainIntent(intent); + if (subIntent == null) { + return false; + } + return startActivityForResult(delegate, subIntent, requestCode); + } + } + + private interface IStartActivityDelegate { + + void startActivity(@NonNull Intent intent); + + void startActivityForResult(@NonNull Intent intent, int requestCode); + } + + private static class StartActivityDelegateContextImpl implements IStartActivityDelegate { + + private final Context mContext; + + private StartActivityDelegateContextImpl(@NonNull Context context) { + mContext = context; + } + + @Override + public void startActivity(@NonNull Intent intent) { + mContext.startActivity(intent); + } + + @Override + public void startActivityForResult(@NonNull Intent intent, int requestCode) { + Activity activity = PermissionUtils.findActivity(mContext); + if (activity != null) { + activity.startActivityForResult(intent, requestCode); + return; + } + startActivity(intent); + } + } + + private static class StartActivityDelegateActivityImpl implements IStartActivityDelegate { + + private final Activity mActivity; + + private StartActivityDelegateActivityImpl(@NonNull Activity activity) { + mActivity = activity; + } + + @Override + public void startActivity(@NonNull Intent intent) { + mActivity.startActivity(intent); + } + + @Override + public void startActivityForResult(@NonNull Intent intent, int requestCode) { + mActivity.startActivityForResult(intent, requestCode); + } + } + + private static class StartActivityDelegateFragmentImpl implements IStartActivityDelegate { + + private final Fragment mFragment; + + private StartActivityDelegateFragmentImpl(@NonNull Fragment fragment) { + mFragment = fragment; + } + + @Override + public void startActivity(@NonNull Intent intent) { + mFragment.startActivity(intent); + } + + @Override + public void startActivityForResult(@NonNull Intent intent, int requestCode) { + mFragment.startActivityForResult(intent, requestCode); + } + } + + private static class StartActivityDelegateSupportFragmentImpl implements IStartActivityDelegate { + + private final android.support.v4.app.Fragment mFragment; + + private StartActivityDelegateSupportFragmentImpl(@NonNull android.support.v4.app.Fragment fragment) { + mFragment = fragment; + } + + @Override + public void startActivity(@NonNull Intent intent) { + mFragment.startActivity(intent); + } + + @Override + public void startActivityForResult(@NonNull Intent intent, int requestCode) { + mFragment.startActivityForResult(intent, requestCode); + } + } +} \ No newline at end of file diff --git a/library/src/main/java/com/hjq/permissions/WindowPermissionCompat.java b/library/src/main/java/com/hjq/permissions/WindowPermissionCompat.java new file mode 100644 index 0000000..e3d39da --- /dev/null +++ b/library/src/main/java/com/hjq/permissions/WindowPermissionCompat.java @@ -0,0 +1,103 @@ +package com.hjq.permissions; + +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; +import android.support.annotation.NonNull; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/XXPermissions + * time : 2023/03/11 + * desc : 悬浮窗权限兼容类 + */ +final class WindowPermissionCompat { + + private static final String OP_SYSTEM_ALERT_WINDOW_FIELD_NAME = "OP_SYSTEM_ALERT_WINDOW"; + private static final int OP_SYSTEM_ALERT_WINDOW_DEFAULT_VALUE = 24; + + static boolean isGrantedPermission(@NonNull Context context) { + if (AndroidVersion.isAndroid6()) { + return Settings.canDrawOverlays(context); + } + + if (AndroidVersion.isAndroid4_4()) { + // 经过测试在 vivo x7 Plus(Android 5.1)和 OPPO A53 (Android 5.1 ColorOs 2.1)的机子上面判断不准确 + // 经过 debug 发现并不是 vivo 和 oppo 修改了 OP_SYSTEM_ALERT_WINDOW 的赋值导致的 + // 估计是 vivo 和 oppo 的机子修改了整个悬浮窗机制,这种就没有办法了 + return PermissionUtils.checkOpNoThrow(context, OP_SYSTEM_ALERT_WINDOW_FIELD_NAME, OP_SYSTEM_ALERT_WINDOW_DEFAULT_VALUE); + } + + return true; + } + + static Intent getPermissionIntent(@NonNull Context context) { + if (AndroidVersion.isAndroid6()) { + if (AndroidVersion.isAndroid11() && PhoneRomUtils.isMiui() && PhoneRomUtils.isMiuiOptimization()) { + // 因为 Android 11 及后面的版本无法直接跳转到具体权限设置页面,只能跳转到悬浮窗权限应用列表,十分地麻烦的,这里做了一下简化 + // miui 做得比较人性化的,不会出现跳转不过去的问题,其他厂商就不一定了,就是不想让你跳转过去 + Intent intent = PermissionIntentManager.getMiuiPermissionPageIntent(context); + // 另外跳转到应用详情页也可以开启悬浮窗权限 + intent = StartActivityManager.addSubIntentToMainIntent(intent, PermissionUtils.getApplicationDetailsIntent(context)); + return intent; + } + + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); + // 在 Android 11 加包名跳转也是没有效果的,官方文档链接: + // https://developer.android.google.cn/reference/android/provider/Settings#ACTION_MANAGE_OVERLAY_PERMISSION + intent.setData(PermissionUtils.getPackageNameUri(context)); + + if (PermissionUtils.areActivityIntent(context, intent)) { + return intent; + } + + intent = PermissionUtils.getApplicationDetailsIntent(context); + return intent; + } + + // 需要注意的是,这里不需要判断鸿蒙,因为鸿蒙 2.0 是基于 Android 10.0,会直接走上面的逻辑,而不会走到下面来 + if (PhoneRomUtils.isEmui()) { + Intent intent = PermissionIntentManager.getEmuiWindowPermissionPageIntent(context); + intent = StartActivityManager.addSubIntentToMainIntent(intent, PermissionUtils.getApplicationDetailsIntent(context)); + return intent; + } + + if (PhoneRomUtils.isMiui()) { + + Intent intent = null; + if (PhoneRomUtils.isMiuiOptimization()) { + // 假设关闭了 miui 优化,就不走这里的逻辑 + intent = PermissionIntentManager.getMiuiWindowPermissionPageIntent(context); + } + + // 小米手机也可以通过应用详情页开启悬浮窗权限(只不过会多一步操作) + intent = StartActivityManager.addSubIntentToMainIntent(intent, PermissionUtils.getApplicationDetailsIntent(context)); + + return intent; + } + + if (PhoneRomUtils.isColorOs()) { + Intent intent = PermissionIntentManager.getColorOsWindowPermissionPageIntent(context); + intent = StartActivityManager.addSubIntentToMainIntent(intent, PermissionUtils.getApplicationDetailsIntent(context)); + return intent; + } + + if (PhoneRomUtils.isOriginOs()) { + Intent intent = PermissionIntentManager.getOriginOsWindowPermissionPageIntent(context); + intent = StartActivityManager.addSubIntentToMainIntent(intent, PermissionUtils.getApplicationDetailsIntent(context)); + return intent; + } + + if (PhoneRomUtils.isOneUi()) { + Intent intent = PermissionIntentManager.getOneUiWindowPermissionPageIntent(context); + intent = StartActivityManager.addSubIntentToMainIntent(intent, PermissionUtils.getApplicationDetailsIntent(context)); + return intent; + } + + // 360 第一部发布的手机是 360 N4,Android 版本是 6.0 了,所以根本不需要跳转到指定的页面开启悬浮窗权限 + // 经过测试,锤子手机 6.0 以下手机的可以直接通过直接跳转到应用详情开启悬浮窗权限 + // 经过测试,魅族手机 6.0 可以直接通过直接跳转到应用详情开启悬浮窗权限 + + return PermissionUtils.getApplicationDetailsIntent(context); + } +} \ No newline at end of file diff --git a/library/src/main/java/com/hjq/permissions/XXPermissions.java b/library/src/main/java/com/hjq/permissions/XXPermissions.java index 68706dd..8c61cc4 100644 --- a/library/src/main/java/com/hjq/permissions/XXPermissions.java +++ b/library/src/main/java/com/hjq/permissions/XXPermissions.java @@ -353,7 +353,7 @@ public static void startPermissionActivity(@NonNull Context context, @NonNull Li if (!(context instanceof Activity)) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } - context.startActivity(intent); + StartActivityManager.startActivity(context, intent); } /* android.app.Activity */ @@ -381,7 +381,7 @@ public static void startPermissionActivity(@NonNull Activity activity, @NonNull List permissions, int requestCode) { Intent intent = PermissionUtils.getSmartPermissionIntent(activity, permissions); - activity.startActivityForResult(intent, requestCode); + StartActivityManager.startActivityForResult(activity, intent, requestCode); } public static void startPermissionActivity(@NonNull Activity activity, @@ -400,7 +400,7 @@ public static void startPermissionActivity(@NonNull Activity activity, @NonNull List permissions, @Nullable OnPermissionPageCallback callback) { if (permissions.isEmpty()) { - activity.startActivity(PermissionUtils.getApplicationDetailsIntent(activity)); + StartActivityManager.startActivity(activity, PermissionUtils.getApplicationDetailsIntent(activity)); return; } PermissionPageFragment.beginRequest(activity, (ArrayList) permissions, callback); @@ -435,11 +435,11 @@ public static void startPermissionActivity(@NonNull Fragment fragment, return; } if (permissions.isEmpty()) { - fragment.startActivity(PermissionUtils.getApplicationDetailsIntent(activity)); + StartActivityManager.startActivity(fragment, PermissionUtils.getApplicationDetailsIntent(activity)); return; } Intent intent = PermissionUtils.getSmartPermissionIntent(activity, permissions); - fragment.startActivityForResult(intent, requestCode); + StartActivityManager.startActivityForResult(fragment, intent, requestCode); } public static void startPermissionActivity(@NonNull Fragment fragment, @@ -465,7 +465,7 @@ public static void startPermissionActivity(@NonNull Fragment fragment, return; } if (permissions.isEmpty()) { - fragment.startActivity(PermissionUtils.getApplicationDetailsIntent(activity)); + StartActivityManager.startActivity(fragment, PermissionUtils.getApplicationDetailsIntent(activity)); return; } PermissionPageFragment.beginRequest(activity, (ArrayList) permissions, callback); @@ -500,11 +500,11 @@ public static void startPermissionActivity(@NonNull android.support.v4.app.Fragm return; } if (permissions.isEmpty()) { - fragment.startActivity(PermissionUtils.getApplicationDetailsIntent(activity)); + StartActivityManager.startActivity(fragment, PermissionUtils.getApplicationDetailsIntent(activity)); return; } Intent intent = PermissionUtils.getSmartPermissionIntent(activity, permissions); - fragment.startActivityForResult(intent, requestCode); + StartActivityManager.startActivityForResult(fragment, intent, requestCode); } public static void startPermissionActivity(@NonNull android.support.v4.app.Fragment fragment, @@ -530,7 +530,7 @@ public static void startPermissionActivity(@NonNull android.support.v4.app.Fragm return; } if (permissions.isEmpty()) { - fragment.startActivity(PermissionUtils.getApplicationDetailsIntent(activity)); + StartActivityManager.startActivity(fragment, PermissionUtils.getApplicationDetailsIntent(activity)); return; } PermissionPageFragment.beginRequest(activity, (ArrayList) permissions, callback);