Преглед изворни кода

v1.0.2开发:权限框架开发

#Suyghur пре 3 година
родитељ
комит
193c6a5d2b

+ 8 - 16
build.gradle

@@ -1,32 +1,22 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
+apply from: "config.gradle"
+
 buildscript {
 
     ext {
-        USE_ANDROIDX_VOLLEY = false
-        // 混淆开关
-        MINIFY_ENABLE = true
-        // ndk版本
-        NDK_VERSION = '21.4.7075529'
-        // kotlin版本
-        KOTLIN_VERSION = '1.4.20'
-        // compileSdkVersion
-        COMPILE_SDK_VERSION = 30
-        // buildToolsVersion
-        BUILD_TOOLS_VERSION = '30.0.3'
-        // minSdkVersion
-        MIN_SDK_VERSION = 16
-        // targetSdkVersion
-        TARGET_SDK_VERSION = 30
+        kotlin_version = '1.5.30'
     }
 
     repositories {
         google()
         mavenCentral()
         jcenter()
+        maven { url 'https://maven.aliyun.com/repository/public' }
+        maven { url 'https://jitpack.io' }
     }
     dependencies {
         classpath "com.android.tools.build:gradle:4.1.3"
-        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
     }
@@ -37,6 +27,8 @@ allprojects {
         google()
         mavenCentral()
         jcenter()
+        maven { url 'https://maven.aliyun.com/repository/public' }
+        maven { url 'https://jitpack.io' }
     }
 }
 

+ 39 - 0
config.gradle

@@ -0,0 +1,39 @@
+ext {
+
+    tag = [
+            userAndroidXVolley: false,
+            minify            : true
+    ]
+
+    android = [
+            compileSdkVersion: 30,
+            buildToolsVersion: '30.0.3',
+            minSdkVersion    : 16,
+            targetSdkVersion : 30,
+            versionCode      : 1,
+            versionName      : '1.0'
+    ]
+
+    ktx = [
+            stdlib    : "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version",
+            coroutines: 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0',
+            core      : 'androidx.core:core-ktx:1.6.0'
+    ]
+
+    ui = [
+            appcompat         : 'androidx.appcompat:appcompat:1.3.1',
+            material          : 'com.google.android.material:material:1.4.0',
+            constraintlayout  : 'androidx.constraintlayout:constraintlayout:2.1.0',
+            swiperefreshlayout: 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0',
+            recyclerview      : 'androidx.recyclerview:recyclerview:1.2.1',
+            cardview          : 'androidx.cardview:cardview:1.0.0'
+    ]
+
+    other = [
+            cronet: 'org.chromium.net:cronet-embedded:76.3809.111'
+    ]
+
+
+    ktxLibs = ktx.values()
+    otherLibs = other.values()
+}

+ 7 - 6
demo/build.gradle

@@ -8,15 +8,15 @@ def keystoreProperties = new Properties()
 keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
 
 android {
-    compileSdkVersion COMPILE_SDK_VERSION
-    buildToolsVersion BUILD_TOOLS_VERSION
+    compileSdkVersion rootProject.ext.android.compileSdkVersion
+    buildToolsVersion rootProject.ext.android.buildToolsVersion
 
     defaultConfig {
         applicationId "com.yyxx.support.demo"
-        minSdkVersion MIN_SDK_VERSION
-        targetSdkVersion TARGET_SDK_VERSION
-        versionCode 1
-        versionName "1.0"
+        minSdkVersion rootProject.ext.android.minSdkVersion
+        targetSdkVersion rootProject.ext.android.targetSdkVersion
+        versionCode rootProject.ext.android.versionCode
+        versionName rootProject.ext.android.versionName
 
         ndk {
             // 设置支持的SO库架构
@@ -75,4 +75,5 @@ dependencies {
     implementation project(':library_support')
     implementation files('../libs/oaid_sdk_1.0.25.jar')
     implementation files('../libs/mmkv-static-1.2.8.jar')
+    implementation files('../libs/android-support-v4.jar')
 }

+ 48 - 5
demo/src/main/AndroidManifest.xml

@@ -2,12 +2,35 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     package="com.yyxx.support.demo">
+    <uses-permission android:name="android.permission.CAMERA" />
 
-    <!-- 网络权限 -->
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+
+    <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
+    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
+
+    <uses-permission
+        android:name="android.permission.WRITE_SETTINGS"
+        tools:ignore="ProtectedPermissions" />
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
 
     <!-- msa sdk -->
     <uses-sdk tools:overrideLibrary="com.zui.opendeviceidlibrary" />
@@ -41,6 +64,26 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity
+            android:name=".PermissionActivity"
+            android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation"
+            android:exported="true"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name="cn.yyxx.support.permission.PermissionKitActivity"
+            android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation"
+            android:imeOptions="flagNoFullscreen|flagNoExtractUi"
+            android:launchMode="singleTask"
+            android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
+            android:windowSoftInputMode="adjustResize|stateHidden|stateVisible" />
+
         <service
             android:name=".FloatViewService"
             android:exported="true" />

+ 3 - 9
demo/src/main/java/com/yyxx/support/demo/DemoActivity.kt

@@ -13,6 +13,7 @@ import cn.yyxx.support.encryption.rsa.RsaUtils
 import cn.yyxx.support.hawkeye.LogUtils
 import cn.yyxx.support.hawkeye.OwnDebugUtils
 import cn.yyxx.support.msa.MsaDeviceIdsHandler
+import cn.yyxx.support.permission.PermissionKitActivity.start
 import cn.yyxx.support.ui.scaleprogress.ScaleLoadingView
 import cn.yyxx.support.volley.VolleySingleton
 import cn.yyxx.support.volley.source.Response
@@ -35,7 +36,7 @@ class DemoActivity : Activity(), View.OnClickListener {
         Item(4, "隐藏浮标"),
         Item(5, "MMKV测试 encode"),
         Item(6, "MMKV测试 decode"),
-        Item(7, "test")
+        Item(7, "权限测试")
 
     )
 
@@ -149,14 +150,7 @@ class DemoActivity : Activity(), View.OnClickListener {
                     }
                 }
                 7 -> {
-                    val rsaKey =
-                        "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3eXf1JxdFtx6c8AJTdlSverL8WqRE11yFB6Q+GbQeEVXjSCgQN48qePat7mXbH4LAtjaSEqXHruP4hJO8777wYtEKNKIN2VZgWQElrllAuAtaHyA+UGKwulOKmkR8k1Oxmfd46fnQBwzy+Giab4lqQRQAObCT0QtUrlrsU1U+zwIDAQAB"
-
-//            String unamePwd = SDKDrive.getInstance().RsaEncrypt(userName + "|" + pwd);
-
-//            String unamePwd = SDKDrive.getInstance().RsaEncrypt(userName + "|" + pwd);
-                    val unamePwd = RsaUtils.encryptByPublicKey("test1231|a123456", rsaKey)
-                    LogUtils.d(unamePwd)
+                   PermissionActivity.start(this@DemoActivity)
                 }
             }
         }

+ 244 - 0
demo/src/main/java/com/yyxx/support/demo/PermissionActivity.kt

@@ -0,0 +1,244 @@
+package com.yyxx.support.demo
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.Toast
+import cn.yyxx.support.hawkeye.LogUtils
+import cn.yyxx.support.permission.IPermissionCallback
+import cn.yyxx.support.permission.Permission
+import cn.yyxx.support.permission.PermissionKit
+import cn.yyxx.support.permission.PermissionKitActivity
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/10/31
+ */
+class PermissionActivity : Activity(), View.OnClickListener {
+
+    private val events: MutableList<Item> = mutableListOf(
+        Item(0, "申请单个危险权限"),
+        Item(1, "申请多个危险权限"),
+        Item(2, "申请定位权限组"),
+        Item(3, "申请新版存储权限"),
+        Item(4, "申请旧版存储权限"),
+        Item(5, "申请安装包权限"),
+        Item(6, "申请悬浮窗权限"),
+        Item(7, "申请通知栏权限"),
+        Item(8, "申请系统设置权限"),
+        Item(9, "跳转到应用详情页")
+    )
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        val layout = LinearLayout(this)
+        layout.orientation = LinearLayout.VERTICAL
+        for (event in events) {
+            with(Button(this)) {
+                tag = event.id
+                text = event.name
+                setOnClickListener(this@PermissionActivity)
+                layout.addView(this)
+            }
+        }
+        setContentView(layout)
+    }
+
+
+    override fun onStart() {
+        super.onStart()
+        LogUtils.d("${PermissionActivity::class.java.simpleName}.onStart")
+    }
+
+    override fun onResume() {
+        super.onResume()
+        LogUtils.d("${PermissionActivity::class.java.simpleName}.onResume")
+    }
+
+    override fun onPause() {
+        super.onPause()
+        LogUtils.d("${PermissionActivity::class.java.simpleName}.onPause")
+    }
+
+    override fun onRestart() {
+        super.onRestart()
+        LogUtils.d("${PermissionActivity::class.java.simpleName}.onRestart")
+    }
+
+    override fun onStop() {
+        super.onStop()
+        LogUtils.d("${PermissionActivity::class.java.simpleName}.onStop")
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        LogUtils.d("${PermissionActivity::class.java.simpleName}.onDestroy")
+    }
+
+
+    override fun onClick(v: View?) {
+        v?.apply {
+            when (tag as Int) {
+                0 -> {
+                    PermissionKit.with().permission(Permission.CAMERA).request(this@PermissionActivity, object : IPermissionCallback {
+
+                        override fun onGranted(permissions: MutableList<String>?, all: Boolean) {
+                            if (all) {
+                                toast("获取拍照权限成功")
+                            }
+                        }
+
+                        override fun onDenied(permissions: MutableList<String>?, never: Boolean) {
+
+                        }
+
+                        override fun onProxyFinish() {
+                            LogUtils.d("onProxyFinish")
+                        }
+
+                    })
+                }
+                1 -> {
+                    PermissionKit.with().permission(Permission.RECORD_AUDIO).permission(Permission.Group.CALENDAR)
+                        .request(this@PermissionActivity, object : IPermissionCallback {
+                            override fun onGranted(permissions: MutableList<String>?, all: Boolean) {
+                                toast("获取录音和日历权限成功")
+                            }
+
+                            override fun onDenied(permissions: MutableList<String>?, never: Boolean) {
+                            }
+
+                            override fun onProxyFinish() {
+                            }
+                        })
+                }
+//                2 -> {
+//                    PermissionKit.with().permission(Permission.ACCESS_BACKGROUND_LOCATION).request(this@PermissionActivity, object : IPermissionCallback {
+//                        override fun onGranted(permissions: ArrayList<String>, all: Boolean) {
+//                            toast("获取定位权限成功")
+//                        }
+//
+//                        override fun onDenied(permissions: ArrayList<String>, never: Boolean) {
+//                        }
+//
+//                        override fun onProxyFinish() {
+//                        }
+//                    })
+//                }
+//                3 -> {
+//                    val delayMillis = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+//                        toast("当前版本不是 Android 11 以上,会自动变更为旧版的请求方式")
+//                        2000L
+//                    } else {
+//                        0L
+//                    }
+//                    postDelayed({
+//                        //不适配 Android 11 可以这样写permission(Permission.Group.STORAGE)
+//                        //适配 Android 11 需要这样写,这里无需再写 Permission.Group.STORAGE
+//                        Qojqva.with().permission(Permission.MANAGE_EXTERNAL_STORAGE).request(this@PermissionActivity, object : IPermissionCallback {
+//                            override fun onGranted(permissions: ArrayList<String>, all: Boolean) {
+//                                toast("获取存储权限成功")
+//                            }
+//
+//                            override fun onDenied(permissions: ArrayList<String>, never: Boolean) {
+//                            }
+//
+//                            override fun onProxyFinish() {
+//                            }
+//                        })
+//                    }, delayMillis)
+//                }
+//                4 -> {
+//                    val delayMillis = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+//                        toast("当前版本不是 Android 11 以上,会自动变更为旧版的请求方式")
+//                        2000L
+//                    } else {
+//                        0L
+//                    }
+//                    postDelayed({
+//                        //不适配 Android 11 可以这样写permission(Permission.Group.STORAGE)
+//                        //适配 Android 11 需要这样写,这里无需再写 Permission.Group.STORAGE
+//                        Qojqva.with().permission(Permission.Group.STORAGE).request(this@PermissionActivity, object : IPermissionCallback {
+//                            override fun onGranted(permissions: ArrayList<String>, all: Boolean) {
+//                                toast("获取存储权限成功")
+//                            }
+//
+//                            override fun onDenied(permissions: ArrayList<String>, never: Boolean) {
+//                            }
+//
+//                            override fun onProxyFinish() {
+//                            }
+//                        })
+//                    }, delayMillis)
+//                }
+//                5 -> {
+//                    Qojqva.with().permission(Permission.REQUEST_INSTALL_PACKAGES).request(this@PermissionActivity, object : IPermissionCallback {
+//                        override fun onGranted(permissions: ArrayList<String>, all: Boolean) {
+//                            toast("获取安装包权限成功")
+//                        }
+//
+//                        override fun onDenied(permissions: ArrayList<String>, never: Boolean) {
+//                        }
+//
+//                        override fun onProxyFinish() {
+//                        }
+//                    })
+//                }
+//                6 -> {
+//                    Qojqva.with().permission(Permission.SYSTEM_ALERT_WINDOW).request(this@PermissionActivity, object : IPermissionCallback {
+//                        override fun onGranted(permissions: ArrayList<String>, all: Boolean) {
+//                            toast("获取悬浮窗权限成功")
+//                        }
+//
+//                        override fun onDenied(permissions: ArrayList<String>, never: Boolean) {
+//                        }
+//
+//                        override fun onProxyFinish() {
+//                        }
+//                    })
+//                }
+//                7 -> {
+//                    Qojqva.with().permission(Permission.NOTIFICATION_SERVICE).request(this@PermissionActivity, object : IPermissionCallback {
+//                        override fun onGranted(permissions: ArrayList<String>, all: Boolean) {
+//                            toast("获取通知栏权限成功")
+//                        }
+//
+//                        override fun onDenied(permissions: ArrayList<String>, never: Boolean) {
+//                        }
+//
+//                        override fun onProxyFinish() {
+//                        }
+//                    })
+//                }
+//                8 -> {
+//                    Qojqva.with().permission(Permission.WRITE_SETTINGS).request(this@PermissionActivity, object : IPermissionCallback {
+//                        override fun onGranted(permissions: ArrayList<String>, all: Boolean) {
+//                            toast("获取系统设置权限成功")
+//                        }
+//
+//                        override fun onDenied(permissions: ArrayList<String>, never: Boolean) {
+//                        }
+//
+//                        override fun onProxyFinish() {
+//                        }
+//                    })
+//                }
+//                9 -> PermissionActivity.startPermissionActivity(this@PermissionActivity)
+            }
+        }
+    }
+
+    private fun toast(msg: String) {
+        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
+    }
+
+    companion object {
+        fun start(context: Context) {
+            context.startActivity(Intent(context, PermissionActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+        }
+    }
+}

+ 13 - 8
library_support/build.gradle

@@ -3,17 +3,17 @@ plugins {
 }
 
 android {
-    compileSdkVersion COMPILE_SDK_VERSION
-    buildToolsVersion BUILD_TOOLS_VERSION
+    compileSdkVersion rootProject.ext.android.compileSdkVersion
+    buildToolsVersion rootProject.ext.android.buildToolsVersion
 
     defaultConfig {
-        minSdkVersion MIN_SDK_VERSION
-        targetSdkVersion TARGET_SDK_VERSION
+        minSdkVersion rootProject.ext.android.minSdkVersion
+        targetSdkVersion rootProject.ext.android.targetSdkVersion
     }
 
     buildTypes {
         release {
-            minifyEnabled MINIFY_ENABLE
+            minifyEnabled rootProject.ext.tag.minify
             proguardFiles 'proguard-rules.pro'
         }
     }
@@ -36,15 +36,20 @@ android {
         preDexLibraries = false
     }
 
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
     //api23以上使用 httpClient
     useLibrary 'org.apache.http.legacy'
 }
 
 dependencies {
     compileOnly files('../libs/oaid_sdk_1.0.25.jar')
-    if (USE_ANDROIDX_VOLLEY) {
-        implementation "org.chromium.net:cronet-embedded:76.3809.111"
-        implementation 'androidx.core:core:1.5.0'
+    if (rootProject.ext.tag.useAndroidXVolley) {
+        implementation rootProject.ext.other['cronet']
+        implementation rootProject.ext.ktx['core']
         api files('../libs/yyxx_support_volleyx_1.0.0.jar')
     } else {
         compileOnly files('../libs/android-support-v4.jar')

+ 214 - 0
library_support/src/main/java/cn/yyxx/support/permission/BasePermissionInterceptor.java

@@ -0,0 +1,214 @@
+package cn.yyxx.support.permission;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.os.Build;
+import android.support.v4.app.FragmentActivity;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/10/31
+ */
+public class BasePermissionInterceptor implements IPermissionInterceptor {
+    @Override
+    public void requestPermissions(FragmentActivity activity, List<String> permissions, IPermissionCallback callback) {
+        PermissionKitFragment.beginRequest(activity, new ArrayList<>(permissions), callback);
+
+    }
+
+    @Override
+    public void grantedPermissions(FragmentActivity activity, List<String> permissions, boolean all, IPermissionCallback callback) {
+        callback.onGranted(permissions, all);
+        if (all) {
+            PermissionKitActivity.finish(activity);
+        }
+    }
+
+    @Override
+    public void deniedPermissions(FragmentActivity activity, List<String> permissions, boolean never, IPermissionCallback callback) {
+        if (never) {
+            showPermissionDialog(activity, permissions);
+            return;
+        }
+        if (permissions.size() == 1 && Permission.ACCESS_BACKGROUND_LOCATION.equals(permissions.get(0))) {
+            Toast.makeText(activity, "没有授予后台定位权限,请您选择\"始终允许\"", Toast.LENGTH_SHORT).show();
+            PermissionKitActivity.finish(activity);
+            return;
+        }
+        Toast.makeText(activity, "授权失败,请正确授予权限", Toast.LENGTH_SHORT).show();
+        PermissionKitActivity.finish(activity);
+    }
+
+    /**
+     * 显示授权对话框
+     */
+    protected void showPermissionDialog(FragmentActivity activity, List<String> permissions) {
+        // 这里的 Dialog 只是示例,没有用 DialogFragment 来处理 Dialog 生命周期
+        new AlertDialog.Builder(activity)
+                .setTitle("授权提醒")
+                .setCancelable(false)
+                .setMessage(getPermissionHint(permissions))
+                .setPositiveButton("前往授权", new DialogInterface.OnClickListener() {
+
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        dialog.dismiss();
+                        PermissionKit.startPermissionActivity(activity, permissions);
+                    }
+                })
+                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        dialog.dismiss();
+                        PermissionKitActivity.finish(activity);
+                    }
+                })
+                .show();
+    }
+
+    private String getPermissionHint(List<String> permissions) {
+        if (permissions == null || permissions.size() == 0) {
+            return "获取权限失败,请手动授予权限";
+        }
+        ArrayList<String> hints = new ArrayList<>();
+        String hint = "";
+        for (String permission : permissions) {
+            switch (permission) {
+                case Permission.READ_EXTERNAL_STORAGE:
+                case Permission.WRITE_EXTERNAL_STORAGE:
+                case Permission.MANAGE_EXTERNAL_STORAGE:
+                    hint = "存储权限";
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                    break;
+                case Permission.CAMERA:
+                    hint = "相机权限";
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                    break;
+                case Permission.RECORD_AUDIO: {
+                    hint = "麦克风权限";
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                }
+                break;
+                case Permission.ACCESS_FINE_LOCATION:
+                case Permission.ACCESS_COARSE_LOCATION:
+                case Permission.ACCESS_BACKGROUND_LOCATION:
+                    if (!permissions.contains(Permission.ACCESS_FINE_LOCATION) && !permissions.contains(Permission.ACCESS_COARSE_LOCATION)) {
+                        hint = "后台定位权限";
+                    } else {
+                        hint = "定位权限";
+                    }
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                    break;
+                case Permission.READ_PHONE_STATE:
+                case Permission.CALL_PHONE:
+                case Permission.ADD_VOICEMAIL:
+                case Permission.USE_SIP:
+                case Permission.READ_PHONE_NUMBERS:
+                case Permission.ANSWER_PHONE_CALLS:
+                    hint = "电话权限";
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                    break;
+                case Permission.GET_ACCOUNTS:
+                case Permission.READ_CONTACTS:
+                case Permission.WRITE_CONTACTS:
+                    hint = "通讯录权限";
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                    break;
+                case Permission.READ_CALENDAR:
+                case  Permission.WRITE_CALENDAR:
+                    hint = "日历权限";
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                    break;
+                case Permission.READ_CALL_LOG:
+                case Permission.WRITE_CALL_LOG:
+                case Permission.PROCESS_OUTGOING_CALLS:
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                        hint = "通话记录权限";
+                    } else {
+                        hint = "电话权限";
+                    }
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                    break;
+                case Permission.BODY_SENSORS:
+                    hint = "身体传感器权限";
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                    break;
+                case Permission.ACTIVITY_RECOGNITION:
+                    hint = "健身运动权限";
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                    break;
+                case Permission.SEND_SMS:
+                case Permission.RECEIVE_SMS:
+                case Permission.READ_SMS:
+                case Permission.RECEIVE_WAP_PUSH:
+                case Permission.RECEIVE_MMS:
+                    hint = "短信权限";
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                    break;
+                case Permission.REQUEST_INSTALL_PACKAGES:
+                    hint = "安装应用权限";
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                    break;
+                case Permission.NOTIFICATION_SERVICE:
+                    hint = "通知栏权限";
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                    break;
+                case Permission.SYSTEM_ALERT_WINDOW:
+                    hint = "悬浮窗权限";
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                    break;
+                case Permission.WRITE_SETTINGS:
+                    hint = "系统设置权限";
+                    if (!hints.contains(hint)) {
+                        hints.add(hint);
+                    }
+                    break;
+            }
+        }
+        if (hints.size() != 0) {
+            StringBuilder builder = new StringBuilder();
+            for (String text : hints) {
+                if (builder.length() == 0) {
+                    builder.append(text);
+                } else {
+                    builder.append("、").append(text);
+                }
+            }
+            builder.append(" ");
+            return "获取权限失败,请手动授予" + builder.toString();
+        }
+        return "获取权限失败,请手动授予";
+    }
+}

+ 31 - 0
library_support/src/main/java/cn/yyxx/support/permission/IPermissionCallback.java

@@ -0,0 +1,31 @@
+package cn.yyxx.support.permission;
+
+import java.util.List;
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/10/29
+ */
+public interface IPermissionCallback {
+
+    /**
+     * 有权限被同意授予时回调
+     *
+     * @param permissions 请求成功的权限组
+     * @param all         是否全部授予了
+     */
+    void onGranted(List<String> permissions, boolean all);
+
+    /**
+     * 有权限被拒绝授予时回调
+     *
+     * @param permissions 请求失败的权限组
+     * @param never       是否有某个权限被永久拒绝了
+     */
+    void onDenied(List<String> permissions, boolean never);
+
+    /**
+     * 代理Activity是否结束
+     */
+    void onProxyFinish();
+}

+ 28 - 0
library_support/src/main/java/cn/yyxx/support/permission/IPermissionInterceptor.java

@@ -0,0 +1,28 @@
+package cn.yyxx.support.permission;
+
+import android.support.v4.app.FragmentActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/10/29
+ */
+public interface IPermissionInterceptor {
+
+    /**
+     * 权限申请拦截,可在此处先弹 Dialog 再申请权限
+     */
+    void requestPermissions(FragmentActivity activity, List<String> permissions, IPermissionCallback callback);
+
+    /**
+     * 权限授予回调拦截,参见 {@link IPermissionCallback#onGranted(List, boolean)}
+     */
+    void grantedPermissions(FragmentActivity activity, List<String> permissions, boolean all, IPermissionCallback callback);
+
+    /**
+     * 权限拒绝回调拦截,参见 {@link IPermissionCallback#onDenied(List, boolean)}
+     */
+    void deniedPermissions(FragmentActivity activity, List<String> permissions, boolean never, IPermissionCallback callback);
+}

+ 263 - 0
library_support/src/main/java/cn/yyxx/support/permission/Permission.java

@@ -0,0 +1,263 @@
+package cn.yyxx.support.permission;
+
+import android.Manifest;
+
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/10/29
+ * <p>
+ * 危险权限和特殊权限常量集,参考 {@link Manifest.permission}
+ * <a href="https://developer.android.google.cn/reference/android/Manifest.permission?hl=zh_cn">文档1</a>
+ * <a href="https://developer.android.google.cn/guide/topics/permissions/overview?hl=zh-cn#normal-dangerous">文档2</a>
+ * </p>
+ */
+@SuppressWarnings("unused")
+public final class Permission {
+
+    private Permission() {
+    }
+
+    /**
+     * 文件管理权限(特殊权限,需要 Android 11 及以上)
+     * <p>
+     * <a href="https://support.google.com/googleplay/android-developer/answer/9956427">如果你的应用需要上架 GooglePlay,那么需要详细查看</a>
+     */
+    public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE";
+
+    /**
+     * 安装应用权限(特殊权限,需要 Android 8.0 及以上)
+     * <p>
+     * <a href="https://cloud.tencent.com/developer/news/637591">Android 11 特性调整,安装外部来源应用需要重启 App</a>
+     * 经过实践,Android 12 已经修复了此问题,授权或者取消授权后应用并不会重启
+     */
+    public static final String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
+
+    /**
+     * 通知栏权限(特殊权限,需要 Android 6.0 及以上,注意此权限不需要在清单文件中注册也能申请)
+     */
+    public static final String NOTIFICATION_SERVICE = "android.permission.NOTIFICATION_SERVICE";
+
+    /**
+     * 悬浮窗权限(特殊权限,需要 Android 6.0 及以上)
+     * <p>
+     * 在 Android 10 及之前的版本能跳转到应用悬浮窗设置页面,而在 Android 11 及之后的版本只能跳转到系统设置悬浮窗管理列表了
+     * <a href="https://developer.android.google.cn/reference/android/provider/Settings#ACTION_MANAGE_OVERLAY_PERMISSION">具体详情请看官方文档解释</a>
+     */
+    public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
+
+    /**
+     * 系统设置权限(特殊权限,需要 Android 6.0 及以上)
+     */
+    public static final String WRITE_SETTINGS = "android.permission.WRITE_SETTINGS";
+
+    /**
+     * 读取外部存储
+     */
+    public static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE";
+    /**
+     * 写入外部存储
+     */
+    public static final String WRITE_EXTERNAL_STORAGE = "android.permission.WRITE_EXTERNAL_STORAGE";
+
+    /**
+     * 相机权限
+     */
+    public static final String CAMERA = "android.permission.CAMERA";
+
+    /**
+     * 麦克风权限
+     */
+    public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
+
+    /**
+     * 获取精确位置
+     */
+    public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
+    /**
+     * 获取粗略位置
+     */
+    public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
+    /**
+     * 在后台获取位置(需要 Android 10.0 及以上)
+     * <p>
+     * 需要注意的是:如果你的 App 只在前台状态下使用定位功能,请不要申请该权限
+     */
+    public static final String ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION";
+
+    /**
+     * 蓝牙扫描权限(需要 Android 12.0 及以上)
+     * <p>
+     * 为了 Android 12 以下兼容版本,请在清单文件中注册 {@link Manifest.permission#BLUETOOTH_ADMIN } 权限
+     * 还有 Android 12 以下设备,获取蓝牙扫描结果需要模糊定位权限,框架会自动在旧的安卓设备上自动添加此权限进行动态申请
+     */
+    public static final String BLUETOOTH_SCAN = "android.permission.BLUETOOTH_SCAN";
+    /**
+     * 蓝牙连接权限(需要 Android 12.0 及以上)
+     * <p>
+     * 为了 Android 12 以下兼容版本,请在清单文件中注册 {@link Manifest.permission#BLUETOOTH } 权限
+     */
+    public static final String BLUETOOTH_CONNECT = "android.permission.BLUETOOTH_CONNECT";
+    /**
+     * 蓝牙广播权限(需要 Android 12.0 及以上)
+     * <p>
+     * 将当前设备的蓝牙进行广播,供其他设备扫描时需要用到该权限
+     */
+    public static final String BLUETOOTH_ADVERTISE = "android.permission.BLUETOOTH_ADVERTISE";
+
+    /**
+     * 读取联系人
+     */
+    public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
+    /**
+     * 修改联系人
+     */
+    public static final String WRITE_CONTACTS = "android.permission.WRITE_CONTACTS";
+    /**
+     * 访问账户列表
+     */
+    public static final String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
+
+    /**
+     * 读取日历
+     */
+    public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
+    /**
+     * 修改日历
+     */
+    public static final String WRITE_CALENDAR = "android.permission.WRITE_CALENDAR";
+
+    /**
+     * 读取照片中的地理位置(需要 Android 10.0 及以上)
+     * <p>
+     * 需要注意的是:如果这个权限申请成功了但是不能正常读取照片的地理信息,那么需要先申请存储权限:
+     * <p>
+     * 如果项目 targetSdkVersion <= 29 需要申请 {@link Group#STORAGE}
+     * 如果项目 targetSdkVersion >= 30 需要申请 {@link Permission#MANAGE_EXTERNAL_STORAGE}
+     */
+    public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION";
+
+    /**
+     * 读取电话状态
+     * <p>
+     * 需要注意的是:这个权限在某些手机上面是没办法获取到的,因为某些系统禁止应用获得该权限
+     * 所以你要是申请了这个权限之后没有弹授权框,而是直接回调授权失败方法
+     * 请不要惊慌,这个不是 Bug、不是 Bug、不是 Bug,而是正常现象
+     * <p>
+     * 后续情况汇报:有人反馈在 iQOO 手机上面获取不到该权限,在清单文件加入下面这个权限就可以了(这里只是做记录,并不代表这种方式就一定有效果)
+     * <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
+     */
+    public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+    /**
+     * 拨打电话
+     */
+    public static final String CALL_PHONE = "android.permission.CALL_PHONE";
+    /**
+     * 读取通话记录
+     */
+    public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
+    /**
+     * 修改通话记录
+     */
+    public static final String WRITE_CALL_LOG = "android.permission.WRITE_CALL_LOG";
+    /**
+     * 添加语音邮件
+     */
+    public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
+    /**
+     * 使用SIP视频
+     */
+    public static final String USE_SIP = "android.permission.USE_SIP";
+    /**
+     * 处理拨出电话
+     *
+     * @deprecated 在 Android 10 已经过时,<a href="https://developer.android.google.cn/reference/android/Manifest.permission?hl=zh_cn#PROCESS_OUTGOING_CALLS">请见</a>
+     */
+    public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
+    /**
+     * 接听电话(需要 Android 8.0 及以上,Android 8.0 以下可以采用模拟耳机按键事件来实现接听电话,这种方式不需要权限)
+     */
+    public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
+    /**
+     * 读取手机号码(需要 Android 8.0 及以上)
+     */
+    public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
+
+    /**
+     * 使用传感器
+     */
+    public static final String BODY_SENSORS = "android.permission.BODY_SENSORS";
+    /**
+     * 获取活动步数(需要 Android 10.0 及以上)
+     */
+    public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
+
+    /**
+     * 发送短信
+     */
+    public static final String SEND_SMS = "android.permission.SEND_SMS";
+    /**
+     * 接收短信
+     */
+    public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
+    /**
+     * 读取短信
+     */
+    public static final String READ_SMS = "android.permission.READ_SMS";
+    /**
+     * 接收 WAP 推送消息
+     */
+    public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
+    /**
+     * 接收彩信
+     */
+    public static final String RECEIVE_MMS = "android.permission.RECEIVE_MMS";
+
+    /**
+     * 允许呼叫应用继续在另一个应用中启动的呼叫(需要 Android 9.0 及以上)
+     */
+    public static final String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER";
+
+    /**
+     * 权限组
+     */
+    public static final class Group {
+
+        /**
+         * 存储权限
+         */
+        public static final String[] STORAGE = new String[]{
+                Permission.READ_EXTERNAL_STORAGE,
+                Permission.WRITE_EXTERNAL_STORAGE};
+
+        /**
+         * 日历权限
+         */
+        public static final String[] CALENDAR = new String[]{
+                Permission.READ_CALENDAR,
+                Permission.WRITE_CALENDAR};
+
+        /**
+         * 联系人权限
+         */
+        public static final String[] CONTACTS = new String[]{
+                Permission.READ_CONTACTS,
+                Permission.WRITE_CONTACTS,
+                Permission.GET_ACCOUNTS};
+
+        /**
+         * 传感器权限
+         */
+        public static final String[] SENSORS = new String[]{
+                Permission.BODY_SENSORS,
+                Permission.ACTIVITY_RECOGNITION};
+
+        /**
+         * 蓝牙权限
+         */
+        public static final String[] BLUETOOTH = new String[]{
+                Permission.BLUETOOTH_SCAN,
+                Permission.BLUETOOTH_CONNECT,
+                Permission.BLUETOOTH_ADVERTISE};
+    }
+}

+ 437 - 0
library_support/src/main/java/cn/yyxx/support/permission/PermissionChecker.java

@@ -0,0 +1,437 @@
+package cn.yyxx.support.permission;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.XmlResourceParser;
+import android.os.Build;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/10/29
+ */
+final class PermissionChecker {
+
+    /**
+     * 检查 Activity 的状态是否正常
+     *
+     * @param debugMode 是否是调试模式
+     * @return 是否检查通过
+     */
+    static boolean checkActivityStatus(Activity activity, boolean debugMode) {
+        // 检查当前 Activity 状态是否是正常的,如果不是则不请求权限
+        if (activity == null) {
+            if (debugMode) {
+                // Context 的实例必须是 Activity 对象
+                throw new IllegalArgumentException("The instance of the context must be an activity object");
+            }
+            return false;
+        }
+
+        if (activity.isFinishing()) {
+            if (debugMode) {
+                // 这个 Activity 对象当前不能是关闭状态,这种情况常出现在执行异步请求后申请权限
+                // 请自行在外层判断 Activity 状态是否正常之后再进入权限申请
+                throw new IllegalStateException("The activity has been finishing, " +
+                        "please manually determine the status of the activity");
+            }
+            return false;
+        }
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed()) {
+            if (debugMode) {
+                // 这个 Activity 对象当前不能是销毁状态,这种情况常出现在执行异步请求后申请权限
+                // 请自行在外层判断 Activity 状态是否正常之后再进入权限申请
+                throw new IllegalStateException("The activity has been destroyed, " +
+                        "please manually determine the status of the activity");
+            }
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 检查传入的权限是否符合要求
+     *
+     * @param requestPermissions 请求的权限组
+     * @param debugMode          是否是调试模式
+     * @return 是否检查通过
+     */
+    static boolean checkPermissionArgument(List<String> requestPermissions, boolean debugMode) {
+        if (requestPermissions == null || requestPermissions.isEmpty()) {
+            if (debugMode) {
+                // 不传权限,就想申请权限?
+                throw new IllegalArgumentException("The requested permission cannot be empty");
+            }
+            return false;
+        }
+        if (debugMode) {
+            List<String> allPermissions = new ArrayList<>();
+            Field[] fields = Permission.class.getDeclaredFields();
+            // 在开启代码混淆之后,反射 Permission 类中的字段会得到空的字段数组
+            // 这个是因为编译后常量会在代码中直接引用,所以 Permission 常量字段在混淆的时候会被移除掉
+            if (fields.length == 0) {
+                return true;
+            }
+            for (Field field : fields) {
+                if (!String.class.equals(field.getType())) {
+                    continue;
+                }
+                try {
+                    allPermissions.add((String) field.get(null));
+                } catch (IllegalAccessException e) {
+                    e.printStackTrace();
+                }
+            }
+            for (String permission : requestPermissions) {
+                if (!allPermissions.contains(permission)) {
+                    // 请不要申请危险权限和特殊权限之外的权限
+                    throw new IllegalArgumentException("The " + permission +
+                            " is not a dangerous permission or special permission, " +
+                            "please do not apply dynamically");
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 检查存储权限
+     *
+     * @param requestPermissions 请求的权限组
+     */
+    static void checkStoragePermission(Context context, List<String> requestPermissions) {
+        // 如果请求的权限中没有包含外部存储相关的权限,那么就直接返回
+        if (!requestPermissions.contains(Permission.MANAGE_EXTERNAL_STORAGE) &&
+                !requestPermissions.contains(Permission.READ_EXTERNAL_STORAGE) &&
+                !requestPermissions.contains(Permission.WRITE_EXTERNAL_STORAGE)) {
+            return;
+        }
+
+        // 是否适配了分区存储
+        boolean scopedStorage = PermissionUtils.isScopedStorage(context);
+
+        XmlResourceParser parser = PermissionUtils.parseAndroidManifest(context);
+        if (parser == null) {
+            return;
+        }
+
+        try {
+
+            do {
+                // 当前节点必须为标签头部
+                if (parser.getEventType() != XmlResourceParser.START_TAG) {
+                    continue;
+                }
+
+                // 当前标签必须为 application
+                if (!"application".equals(parser.getName())) {
+                    continue;
+                }
+
+                int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+
+                boolean requestLegacyExternalStorage = parser.getAttributeBooleanValue(PermissionUtils.ANDROID_NAMESPACE,
+                        "requestLegacyExternalStorage", false);
+                // 如果在已经适配 Android 10 的情况下
+                if (targetSdkVersion >= Build.VERSION_CODES.Q && !requestLegacyExternalStorage &&
+                        (requestPermissions.contains(Permission.MANAGE_EXTERNAL_STORAGE) || !scopedStorage)) {
+                    // 请在清单文件 Application 节点中注册 android:requestLegacyExternalStorage="true" 属性
+                    // 否则就算申请了权限,也无法在 Android 10 的设备上正常读写外部存储上的文件
+                    // 如果你的项目已经全面适配了分区存储,请在清单文件中注册一个 meta-data 属性
+                    // <meta-data android:name="ScopedStorage" android:value="true" /> 来跳过该检查
+                    throw new IllegalStateException("Please register the android:requestLegacyExternalStorage=\"true\" " +
+                            "attribute in the AndroidManifest.xml file");
+                }
+
+                // 如果在已经适配 Android 11 的情况下
+                if (targetSdkVersion >= Build.VERSION_CODES.R &&
+                        !requestPermissions.contains(Permission.MANAGE_EXTERNAL_STORAGE) && !scopedStorage) {
+                    // 1. 适配分区存储的特性,并在清单文件中注册一个 meta-data 属性
+                    // <meta-data android:name="ScopedStorage" android:value="true" />
+                    // 2. 如果不想适配分区存储,则需要使用 Permission.MANAGE_EXTERNAL_STORAGE 来申请权限
+                    // 上面两种方式需要二选一,否则无法在 Android 11 的设备上正常读写外部存储上的文件
+                    // 如果不知道该怎么选择,可以看文档:https://github.com/getActivity/XXPermissions/blob/master/HelpDoc
+                    throw new IllegalArgumentException("Please adapt the scoped storage, " +
+                            "or use the MANAGE_EXTERNAL_STORAGE permission");
+                }
+
+                // 终止循环
+                break;
+
+            } while (parser.next() != XmlResourceParser.END_DOCUMENT);
+
+        } catch (IOException | XmlPullParserException e) {
+            e.printStackTrace();
+        } finally {
+            parser.close();
+        }
+    }
+
+    /**
+     * 检查定位权限
+     *
+     * @param requestPermissions 请求的权限组
+     */
+    static void checkLocationPermission(Context context, List<String> requestPermissions) {
+//        if (context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) {
+//            if (requestPermissions.contains(Permission.ACCESS_FINE_LOCATION) &&
+//                    !requestPermissions.contains(Permission.ACCESS_COARSE_LOCATION)) {
+//                // 如果您的应用以 Android 12 为目标平台并且您请求 ACCESS_FINE_LOCATION 权限,则还必须请求 ACCESS_COARSE_LOCATION 权限
+//                // 官方适配文档:https://developer.android.google.cn/about/versions/12/approximate-location
+//                throw new IllegalArgumentException("If your app targets Android 12 or higher " +
+//                        "and requests the ACCESS_FINE_LOCATION runtime permission, " +
+//                        "you must also request the ACCESS_COARSE_LOCATION permission. " +
+//                        "You must include both permissions in a single runtime request.");
+//            }
+//        }
+
+        if (context.getApplicationInfo().targetSdkVersion >= 31) {
+            if (requestPermissions.contains(Permission.ACCESS_FINE_LOCATION) &&
+                    !requestPermissions.contains(Permission.ACCESS_COARSE_LOCATION)) {
+                // 如果您的应用以 Android 12 为目标平台并且您请求 ACCESS_FINE_LOCATION 权限,则还必须请求 ACCESS_COARSE_LOCATION 权限
+                // 官方适配文档:https://developer.android.google.cn/about/versions/12/approximate-location
+                throw new IllegalArgumentException("If your app targets Android 12 or higher " +
+                        "and requests the ACCESS_FINE_LOCATION runtime permission, " +
+                        "you must also request the ACCESS_COARSE_LOCATION permission. " +
+                        "You must include both permissions in a single runtime request.");
+            }
+        }
+
+        // 判断是否包含后台定位权限
+        if (!requestPermissions.contains(Permission.ACCESS_BACKGROUND_LOCATION)) {
+            return;
+        }
+
+        if (requestPermissions.contains(Permission.ACCESS_COARSE_LOCATION) &&
+                !requestPermissions.contains(Permission.ACCESS_FINE_LOCATION)) {
+            // 申请后台定位权限可以不包含模糊定位权限,但是一定要包含精确定位权限,否则后台定位权限会无法申请,也就是会导致无法弹出授权弹窗
+            // 经过实践,在 Android 12 上这个问题已经被解决了,但是为了兼容 Android 12 以下的设备,还是要那么做,否则在 Android 11 及以下设备会出现异常
+            throw new IllegalArgumentException("The application for background location permissions " +
+                    "must include precise location permissions");
+        }
+
+        for (String permission : requestPermissions) {
+            if (Permission.ACCESS_FINE_LOCATION.equals(permission)
+                    || Permission.ACCESS_COARSE_LOCATION.equals(permission)
+                    || Permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) {
+                continue;
+            }
+
+            // 因为包含了后台定位权限,所以请不要申请和定位无关的权限,因为在 Android 11 上面,后台定位权限不能和其他非定位的权限一起申请
+            // 否则会出现只申请了后台定位权限,其他权限会被回绝掉的情况,因为在 Android 11 上面,后台定位权限是要跳 Activity,并非弹 Dialog
+            // 如果你的项目没有后台定位的需求,请不要一同申请 Permission.ACCESS_BACKGROUND_LOCATION 权限
+            throw new IllegalArgumentException("Because it includes background location permissions, " +
+                    "do not apply for permissions unrelated to location");
+        }
+    }
+
+    /**
+     * 检查targetSdkVersion 是否符合要求
+     *
+     * @param requestPermissions 请求的权限组
+     */
+    static void checkTargetSdkVersion(Context context, List<String> requestPermissions) {
+        // targetSdk 最低版本要求
+        int targetSdkMinVersion;
+        if (requestPermissions.contains(Permission.BLUETOOTH_SCAN) ||
+                requestPermissions.contains(Permission.BLUETOOTH_CONNECT) ||
+                requestPermissions.contains(Permission.BLUETOOTH_ADVERTISE)) {
+//            targetSdkMinVersion = Build.VERSION_CODES.S;
+            targetSdkMinVersion = 31;
+        } else if (requestPermissions.contains(Permission.MANAGE_EXTERNAL_STORAGE)) {
+            // 必须设置 targetSdkVersion >= 30 才能正常检测权限,否则请使用 Permission.Group.STORAGE 来申请存储权限
+            targetSdkMinVersion = Build.VERSION_CODES.R;
+        } else if (requestPermissions.contains(Permission.ACCEPT_HANDOVER)) {
+            targetSdkMinVersion = Build.VERSION_CODES.P;
+        } else if (requestPermissions.contains(Permission.ACCESS_BACKGROUND_LOCATION) ||
+                requestPermissions.contains(Permission.ACTIVITY_RECOGNITION) ||
+                requestPermissions.contains(Permission.ACCESS_MEDIA_LOCATION)) {
+            targetSdkMinVersion = Build.VERSION_CODES.Q;
+        } else if (requestPermissions.contains(Permission.REQUEST_INSTALL_PACKAGES) ||
+                requestPermissions.contains(Permission.ANSWER_PHONE_CALLS) ||
+                requestPermissions.contains(Permission.READ_PHONE_NUMBERS)) {
+            targetSdkMinVersion = Build.VERSION_CODES.O;
+        } else {
+            targetSdkMinVersion = Build.VERSION_CODES.M;
+        }
+
+        // 必须设置正确的 targetSdkVersion 才能正常检测权限
+        if (context.getApplicationInfo().targetSdkVersion < targetSdkMinVersion) {
+            throw new RuntimeException("The targetSdkVersion SDK must be " + targetSdkMinVersion + " or more");
+        }
+    }
+
+    /**
+     * 检查清单文件中所注册的权限是否正常
+     *
+     * @param requestPermissions 请求的权限组
+     */
+    static void checkManifestPermissions(Context context, List<String> requestPermissions) {
+        HashMap<String, Integer> manifestPermissions = PermissionUtils.getManifestPermissions(context);
+        if (manifestPermissions.isEmpty()) {
+            throw new IllegalStateException("No permissions are registered in the AndroidManifest.xml file");
+        }
+
+        int minSdkVersion = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
+                context.getApplicationInfo().minSdkVersion : Build.VERSION_CODES.M;
+
+        for (String permission : requestPermissions) {
+
+            if (Permission.NOTIFICATION_SERVICE.equals(permission)) {
+                // 不检测通知栏权限有没有在清单文件中注册,因为这个权限是框架虚拟出来的,有没有在清单文件中注册都没关系
+                continue;
+            }
+
+            int sdkVersion;
+
+//            if ((sdkVersion = Build.VERSION_CODES.S) >= minSdkVersion) {
+//
+//                if (Permission.BLUETOOTH_SCAN.equals(permission)) {
+//                    checkManifestPermission(manifestPermissions, Manifest.permission.BLUETOOTH_ADMIN, sdkVersion);
+//                    // 这是 Android 12 之前遗留的问题,获取扫描蓝牙的结果需要定位的权限
+//                    checkManifestPermission(manifestPermissions, Manifest.permission.ACCESS_COARSE_LOCATION, sdkVersion);
+//                }
+//
+//                if (Permission.BLUETOOTH_CONNECT.equals(permission)) {
+//                    checkManifestPermission(manifestPermissions, Manifest.permission.BLUETOOTH, sdkVersion);
+//                }
+//
+//                if (Permission.BLUETOOTH_ADVERTISE.equals(permission)) {
+//                    checkManifestPermission(manifestPermissions, Manifest.permission.BLUETOOTH_ADMIN, sdkVersion);
+//                }
+//            }
+
+            if ((sdkVersion =31) >= minSdkVersion) {
+
+                if (Permission.BLUETOOTH_SCAN.equals(permission)) {
+                    checkManifestPermission(manifestPermissions, Manifest.permission.BLUETOOTH_ADMIN, sdkVersion);
+                    // 这是 Android 12 之前遗留的问题,获取扫描蓝牙的结果需要定位的权限
+                    checkManifestPermission(manifestPermissions, Manifest.permission.ACCESS_COARSE_LOCATION, sdkVersion);
+                }
+
+                if (Permission.BLUETOOTH_CONNECT.equals(permission)) {
+                    checkManifestPermission(manifestPermissions, Manifest.permission.BLUETOOTH, sdkVersion);
+                }
+
+                if (Permission.BLUETOOTH_ADVERTISE.equals(permission)) {
+                    checkManifestPermission(manifestPermissions, Manifest.permission.BLUETOOTH_ADMIN, sdkVersion);
+                }
+            }
+
+            if ((sdkVersion = Build.VERSION_CODES.R) >= minSdkVersion) {
+
+                if (Permission.MANAGE_EXTERNAL_STORAGE.equals(permission)) {
+                    checkManifestPermission(manifestPermissions, Permission.READ_EXTERNAL_STORAGE, sdkVersion);
+                    checkManifestPermission(manifestPermissions, Permission.WRITE_EXTERNAL_STORAGE, sdkVersion);
+                }
+            }
+
+            if ((sdkVersion = Build.VERSION_CODES.Q) >= minSdkVersion) {
+
+                if (Permission.ACTIVITY_RECOGNITION.equals(permission)) {
+                    checkManifestPermission(manifestPermissions, Permission.BODY_SENSORS, sdkVersion);
+                }
+            }
+
+            if ((sdkVersion = Build.VERSION_CODES.O) >= minSdkVersion) {
+
+                if (Permission.READ_PHONE_NUMBERS.equals(permission)) {
+                    checkManifestPermission(manifestPermissions, Permission.READ_PHONE_NUMBERS, sdkVersion);
+                }
+            }
+
+            checkManifestPermission(manifestPermissions, permission, Integer.MAX_VALUE);
+        }
+    }
+
+    /**
+     * 检查某个权限注册是否正常,如果是则会抛出异常
+     *
+     * @param manifestPermissions 清单权限组
+     * @param checkPermission     被检查的权限
+     * @param maxSdkVersion       最低要求的 maxSdkVersion
+     */
+    static void checkManifestPermission(HashMap<String, Integer> manifestPermissions, String checkPermission, int maxSdkVersion) {
+        if (!manifestPermissions.containsKey(checkPermission)) {
+            // 动态申请的权限没有在清单文件中注册
+            throw new IllegalStateException("Please register permissions in the AndroidManifest.xml file " +
+                    "<uses-permission android:name=\"" + checkPermission + "\" />");
+        }
+
+        Integer manifestMaxSdkVersion = manifestPermissions.get(checkPermission);
+        if (manifestMaxSdkVersion == null) {
+            return;
+        }
+
+        if (manifestMaxSdkVersion < maxSdkVersion) {
+            // 清单文件中所注册的权限 android:maxSdkVersion 大小不符合最低要求
+            throw new IllegalArgumentException("The AndroidManifest.xml file " +
+                    "<uses-permission android:name=\"" + checkPermission +
+                    "\" android:maxSdkVersion=\"" + manifestMaxSdkVersion +
+                    "\" /> does not meet the requirements, " +
+                    (maxSdkVersion != Integer.MAX_VALUE ? "the minimum requirement for maxSdkVersion is " + maxSdkVersion
+                            : "please delete the android:maxSdkVersion=\"" + manifestMaxSdkVersion + "\" attribute"));
+        }
+    }
+
+    /**
+     * 处理和优化已经过时的权限
+     *
+     * @param requestPermissions 请求的权限组
+     */
+    static void optimizeDeprecatedPermission(List<String> requestPermissions) {
+        if (!PermissionUtils.isAndroid12() &&
+                requestPermissions.contains(Permission.BLUETOOTH_SCAN) &&
+                !requestPermissions.contains(Permission.ACCESS_COARSE_LOCATION)) {
+            // 自动添加定位权限,因为在低版本下获取蓝牙扫描的结果需要此权限
+            requestPermissions.add(Permission.ACCESS_COARSE_LOCATION);
+        }
+
+        // 如果本次申请包含了 Android 12 蓝牙扫描权限
+        if (!PermissionUtils.isAndroid12() && requestPermissions.contains(Permission.BLUETOOTH_SCAN)) {
+            // 这是 Android 12 之前遗留的问题,扫描蓝牙需要定位的权限
+            requestPermissions.add(Permission.ACCESS_COARSE_LOCATION);
+        }
+
+        // 如果本次申请包含了 Android 11 存储权限
+        if (requestPermissions.contains(Permission.MANAGE_EXTERNAL_STORAGE)) {
+
+            if (requestPermissions.contains(Permission.READ_EXTERNAL_STORAGE) ||
+                    requestPermissions.contains(Permission.WRITE_EXTERNAL_STORAGE)) {
+                // 检测是否有旧版的存储权限,有的话直接抛出异常,请不要自己动态申请这两个权限
+                throw new IllegalArgumentException("If you have applied for MANAGE_EXTERNAL_STORAGE permissions, " +
+                        "do not apply for the READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions");
+            }
+
+            if (!PermissionUtils.isAndroid11()) {
+                // 自动添加旧版的存储权限,因为旧版的系统不支持申请新版的存储权限
+                requestPermissions.add(Permission.READ_EXTERNAL_STORAGE);
+                requestPermissions.add(Permission.WRITE_EXTERNAL_STORAGE);
+            }
+        }
+
+        if (!PermissionUtils.isAndroid8() &&
+                requestPermissions.contains(Permission.READ_PHONE_NUMBERS) &&
+                !requestPermissions.contains(Permission.READ_PHONE_STATE)) {
+            // 自动添加旧版的读取电话号码权限,因为旧版的系统不支持申请新版的权限
+            requestPermissions.add(Permission.READ_PHONE_STATE);
+        }
+
+        if (!PermissionUtils.isAndroid10() &&
+                requestPermissions.contains(Permission.ACTIVITY_RECOGNITION) &&
+                !requestPermissions.contains(Permission.BODY_SENSORS)) {
+            // 自动添加传感器权限,因为这个权限是从 Android 10 开始才从传感器权限中剥离成独立权限
+            requestPermissions.add(Permission.BODY_SENSORS);
+        }
+    }
+}

+ 316 - 0
library_support/src/main/java/cn/yyxx/support/permission/PermissionKit.java

@@ -0,0 +1,316 @@
+package cn.yyxx.support.permission;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.support.v4.app.FragmentActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * author : Android 轮子哥
+ * github : https://github.com/getActivity/XXPermissions
+ * time   : 2018/06/15
+ * desc   : Android 危险权限请求类
+ */
+@SuppressWarnings({"unused", "deprecation"})
+public final class PermissionKit {
+
+    /**
+     * 权限设置页跳转请求码
+     */
+    public static final int REQUEST_CODE = 1024 + 1;
+
+    /**
+     * 权限请求拦截器
+     */
+    private static IPermissionInterceptor sInterceptor;
+
+    /**
+     * 当前是否为调试模式
+     */
+    private static Boolean sDebugMode;
+
+    /**
+     * 设置请求的对象
+     */
+    public static PermissionKit with() {
+        return new PermissionKit();
+    }
+
+    /**
+     * 是否为调试模式
+     */
+    public static void setDebugMode(boolean debug) {
+        sDebugMode = debug;
+    }
+
+    private static boolean isDebugMode(Context context) {
+        if (sDebugMode == null) {
+            sDebugMode = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+        }
+        return sDebugMode;
+    }
+
+    /**
+     * 设置全局权限请求拦截器
+     */
+    public static void setInterceptor(IPermissionInterceptor interceptor) {
+        sInterceptor = interceptor;
+    }
+
+    /**
+     * 获取全局权限请求拦截器
+     */
+    public static IPermissionInterceptor getInterceptor() {
+        if (sInterceptor == null) {
+            sInterceptor = new BasePermissionInterceptor() {
+            };
+        }
+        return sInterceptor;
+    }
+
+
+    /**
+     * 权限列表
+     */
+    private List<String> mPermissions;
+
+    /**
+     * 权限请求拦截器
+     */
+    private IPermissionInterceptor mInterceptor;
+
+    /**
+     * 私有化构造函数
+     */
+    private PermissionKit() {
+
+    }
+
+    /**
+     * 设置权限请求拦截器
+     */
+    public PermissionKit interceptor(IPermissionInterceptor interceptor) {
+        mInterceptor = interceptor;
+        return this;
+    }
+
+    /**
+     * 添加权限组
+     */
+    public PermissionKit permission(String... permissions) {
+        return permission(PermissionUtils.asArrayList(permissions));
+    }
+
+    public PermissionKit permission(String[]... permissions) {
+        return permission(PermissionUtils.asArrayLists(permissions));
+    }
+
+    public PermissionKit permission(List<String> permissions) {
+        if (permissions == null || permissions.isEmpty()) {
+            return this;
+        }
+        if (mPermissions == null) {
+            mPermissions = new ArrayList<>(permissions);
+            return this;
+        }
+
+        for (String permission : permissions) {
+            if (mPermissions.contains(permission)) {
+                continue;
+            }
+            mPermissions.add(permission);
+        }
+        return this;
+    }
+
+    /**
+     * 请求权限
+     */
+    public void request(Context context,IPermissionCallback callback) {
+
+        if (mInterceptor == null) {
+            mInterceptor = getInterceptor();
+        }
+        PermissionKitActivity.start(context,mPermissions,callback);
+    }
+
+    /**
+     * 判断一个或多个权限是否全部授予了
+     */
+    public static boolean isGranted(Context context, String... permissions) {
+        return isGranted(context, PermissionUtils.asArrayList(permissions));
+    }
+
+    public static boolean isGranted(Context context, String[]... permissions) {
+        return isGranted(context, PermissionUtils.asArrayLists(permissions));
+    }
+
+    public static boolean isGranted(Context context, List<String> permissions) {
+        return PermissionUtils.isGrantedPermissions(context, permissions);
+    }
+
+    /**
+     * 获取没有授予的权限
+     */
+    public static List<String> getDenied(Context context, String... permissions) {
+        return getDenied(context, PermissionUtils.asArrayList(permissions));
+    }
+
+    public static List<String> getDenied(Context context, String[]... permissions) {
+        return getDenied(context, PermissionUtils.asArrayLists(permissions));
+    }
+
+    public static List<String> getDenied(Context context, List<String> permissions) {
+        return PermissionUtils.getDeniedPermissions(context, permissions);
+    }
+
+    /**
+     * 判断某个权限是否为特殊权限
+     */
+    public static boolean isSpecial(String permission) {
+        return PermissionUtils.isSpecialPermission(permission);
+    }
+
+    /**
+     * 判断一个或多个权限是否被永久拒绝了(注意不能在请求权限之前调用,应该在 {@link IPermissionCallback#onDenied(List, boolean)} 方法中调用)
+     */
+    public static boolean isPermanentDenied(Activity activity, String... permissions) {
+        return isPermanentDenied(activity, PermissionUtils.asArrayList(permissions));
+    }
+
+    public static boolean isPermanentDenied(Activity activity, String[]... permissions) {
+        return isPermanentDenied(activity, PermissionUtils.asArrayLists(permissions));
+    }
+
+    public static boolean isPermanentDenied(Activity activity, List<String> permissions) {
+        return PermissionUtils.isPermissionPermanentDenied(activity, permissions);
+    }
+
+    /* android.content.Context */
+
+    public static void startPermissionActivity(Context context) {
+        startPermissionActivity(context, (List<String>) null);
+    }
+
+    public static void startPermissionActivity(Context context, String... permissions) {
+        startPermissionActivity(context, PermissionUtils.asArrayList(permissions));
+    }
+
+    public static void startPermissionActivity(Context context, String[]... permissions) {
+        startPermissionActivity(context, PermissionUtils.asArrayLists(permissions));
+    }
+
+    /**
+     * 跳转到应用权限设置页
+     *
+     * @param permissions 没有授予或者被拒绝的权限组
+     */
+    public static void startPermissionActivity(Context context, List<String> permissions) {
+        Activity activity = PermissionUtils.findActivity(context);
+        if (activity != null) {
+            startPermissionActivity(activity, permissions);
+            return;
+        }
+        Intent intent = PermissionSettingPage.getSmartPermissionIntent(context, permissions);
+        if (!(context instanceof Activity)) {
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        }
+        context.startActivity(intent);
+    }
+
+    /* android.app.Activity */
+    public static void startPermissionActivity(Activity activity) {
+        startPermissionActivity(activity, (List<String>) null);
+    }
+
+    public static void startPermissionActivity(Activity activity, String... permissions) {
+        startPermissionActivity(activity, PermissionUtils.asArrayList(permissions));
+    }
+
+    public static void startPermissionActivity(Activity activity, String[]... permissions) {
+        startPermissionActivity(activity, PermissionUtils.asArrayLists(permissions));
+    }
+
+    public static void startPermissionActivity(Activity activity, List<String> permissions) {
+        startPermissionActivity(activity, permissions, REQUEST_CODE);
+    }
+
+    /**
+     * 跳转到应用权限设置页
+     *
+     * @param permissions 没有授予或者被拒绝的权限组
+     * @param requestCode Activity 跳转请求码
+     */
+    public static void startPermissionActivity(Activity activity, List<String> permissions, int requestCode) {
+        activity.startActivityForResult(PermissionSettingPage.getSmartPermissionIntent(activity, permissions), requestCode);
+    }
+
+    /* android.app.Fragment */
+
+    public static void startPermissionActivity(Fragment fragment) {
+        startPermissionActivity(fragment, (List<String>) null);
+    }
+
+    public static void startPermissionActivity(Fragment fragment, String... permissions) {
+        startPermissionActivity(fragment, PermissionUtils.asArrayList(permissions));
+    }
+
+    public static void startPermissionActivity(Fragment fragment, String[]... permissions) {
+        startPermissionActivity(fragment, PermissionUtils.asArrayLists(permissions));
+    }
+
+    public static void startPermissionActivity(Fragment fragment, List<String> permissions) {
+        startPermissionActivity(fragment, permissions, REQUEST_CODE);
+    }
+
+    /**
+     * 跳转到应用权限设置页
+     *
+     * @param permissions 没有授予或者被拒绝的权限组
+     * @param requestCode Activity 跳转请求码
+     */
+    public static void startPermissionActivity(Fragment fragment, List<String> permissions, int requestCode) {
+        Activity activity = fragment.getActivity();
+        if (activity == null) {
+            return;
+        }
+        fragment.startActivityForResult(PermissionSettingPage.getSmartPermissionIntent(activity, permissions), requestCode);
+    }
+
+    /* android.support.v4.app.Fragment */
+
+    public static void startPermissionActivity(android.support.v4.app.Fragment fragment) {
+        startPermissionActivity(fragment, (List<String>) null);
+    }
+
+    public static void startPermissionActivity(android.support.v4.app.Fragment fragment, String... permissions) {
+        startPermissionActivity(fragment, PermissionUtils.asArrayList(permissions));
+    }
+
+    public static void startPermissionActivity(android.support.v4.app.Fragment fragment, String[]... permissions) {
+        startPermissionActivity(fragment, PermissionUtils.asArrayLists(permissions));
+    }
+
+    public static void startPermissionActivity(android.support.v4.app.Fragment fragment, List<String> permissions) {
+        startPermissionActivity(fragment, permissions, REQUEST_CODE);
+    }
+
+    /**
+     * 跳转到应用权限设置页
+     *
+     * @param permissions 没有授予或者被拒绝的权限组
+     * @param requestCode Activity 跳转请求码
+     */
+    public static void startPermissionActivity(android.support.v4.app.Fragment fragment, List<String> permissions, int requestCode) {
+        Activity activity = fragment.getActivity();
+        if (activity == null) {
+            return;
+        }
+        fragment.startActivityForResult(PermissionSettingPage.getSmartPermissionIntent(activity, permissions), requestCode);
+    }
+}

+ 124 - 0
library_support/src/main/java/cn/yyxx/support/permission/PermissionKitActivity.java

@@ -0,0 +1,124 @@
+package cn.yyxx.support.permission;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.FragmentActivity;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/10/31
+ */
+public class PermissionKitActivity extends FragmentActivity {
+    private FrameLayout layout = null;
+    private static List<String> mPermissions = null;
+    private static IPermissionCallback mCallback = null;
+
+    public static void start(Context context, List<String> permissions, IPermissionCallback callback) {
+        if (mPermissions != null) {
+            mPermissions.clear();
+            mPermissions = null;
+        }
+        if (mCallback != null) {
+            mCallback = null;
+        }
+        mPermissions = permissions;
+        mCallback = callback;
+        context.startActivity(new Intent(context, PermissionKitActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+    }
+
+    public static void finish(Context context) {
+        if (context instanceof PermissionKitActivity) {
+            ((PermissionKitActivity) context).finish();
+        }
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        layout = new FrameLayout(this);
+        setContentView(layout);
+        doRequestPermissions();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mPermissions != null) {
+            mPermissions.clear();
+            mPermissions = null;
+        }
+        mCallback = null;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == PermissionKit.REQUEST_CODE && !isFinishing()) {
+            if (mCallback != null) {
+                mCallback.onProxyFinish();
+                finish();
+            }
+        }
+    }
+
+    private void doRequestPermissions() {
+        if (mPermissions == null || mPermissions.isEmpty()) {
+            return;
+        }
+        if (mCallback == null) {
+            return;
+        }
+        // 权限请求列表(为什么直接不用字段?因为框架要兼容新旧权限,在低版本下会自动添加旧权限申请)
+        List<String> permissions = new ArrayList<>(mPermissions);
+
+        // 当前是否为调试模式
+        boolean debugMode = (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+
+        // 检查当前 Activity 状态是否是正常的,如果不是则不请求权限
+        FragmentActivity activity = PermissionUtils.findActivity(this);
+        if (!PermissionChecker.checkActivityStatus(activity, debugMode)) {
+            return;
+        }
+
+        // 必须要传入正常的权限或者权限组才能申请权限
+        if (!PermissionChecker.checkPermissionArgument(permissions, debugMode)) {
+            return;
+        }
+
+        if (debugMode) {
+            // 检查申请的存储权限是否符合规范
+            PermissionChecker.checkStoragePermission(this, permissions);
+            // 检查申请的定位权限是否符合规范
+            PermissionChecker.checkLocationPermission(this, permissions);
+            // 检查申请的权限和 targetSdk 版本是否能吻合
+            PermissionChecker.checkTargetSdkVersion(this, permissions);
+        }
+
+        if (debugMode) {
+            // 检测权限有没有在清单文件中注册
+            PermissionChecker.checkManifestPermissions(this, permissions);
+        }
+
+        // 优化所申请的权限列表
+        PermissionChecker.optimizeDeprecatedPermission(permissions);
+
+        if (PermissionUtils.isGrantedPermissions(this, permissions)) {
+            // 证明这些权限已经全部授予过,直接回调成功
+            if (mCallback != null) {
+                mCallback.onGranted(mPermissions, true);
+                activity.finish();
+            }
+            return;
+        }
+
+        // 申请没有授予过的权限
+        PermissionKit.getInterceptor().requestPermissions(activity, permissions, mCallback);
+    }
+}

+ 402 - 0
library_support/src/main/java/cn/yyxx/support/permission/PermissionKitFragment.java

@@ -0,0 +1,402 @@
+package cn.yyxx.support.permission;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.util.ArraySet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/10/29
+ */
+@SuppressWarnings("deprecation")
+@TargetApi(Build.VERSION_CODES.M)
+public final class PermissionKitFragment extends Fragment implements Runnable {
+
+    /**
+     * 请求的权限组
+     */
+    private static final String REQUEST_PERMISSIONS = "request_permissions";
+
+    /**
+     * 请求码(自动生成)
+     */
+    private static final String REQUEST_CODE = "request_code";
+
+    /**
+     * 权限请求码存放集合
+     */
+    private static final ArraySet<Integer> REQUEST_CODE_ARRAY = new ArraySet<>();
+
+    /**
+     * 是否申请了特殊权限
+     */
+    private boolean mSpecialRequest;
+
+    /**
+     * 是否申请了危险权限
+     */
+    private boolean mDangerousRequest;
+
+    /**
+     * 权限申请标记
+     */
+    private boolean mRequestFlag;
+
+    /**
+     * 权限回调对象
+     */
+    private IPermissionCallback mCallBack;
+
+    /**
+     * Activity 屏幕方向
+     */
+    private int mScreenOrientation;
+
+    /**
+     * 开启权限申请
+     */
+    public static void beginRequest(FragmentActivity activity, ArrayList<String> permissions, IPermissionCallback callback) {
+        PermissionKitFragment fragment = new PermissionKitFragment();
+        Bundle bundle = new Bundle();
+        int requestCode;
+        // 请求码随机生成,避免随机产生之前的请求码,必须进行循环判断
+        do {
+            // 新版本的 Support 库限制请求码必须小于 65536
+            // 旧版本的 Support 库限制请求码必须小于 256
+            requestCode = new Random().nextInt((int) Math.pow(2, 8));
+        } while (REQUEST_CODE_ARRAY.contains(requestCode));
+        // 标记这个请求码已经被占用
+        REQUEST_CODE_ARRAY.add(requestCode);
+        bundle.putInt(REQUEST_CODE, requestCode);
+        bundle.putStringArrayList(REQUEST_PERMISSIONS, permissions);
+        fragment.setArguments(bundle);
+        // 设置保留实例,不会因为屏幕方向或配置变化而重新创建
+        fragment.setRetainInstance(true);
+        // 设置权限申请标记
+        fragment.setRequestFlag(true);
+        // 设置权限回调监听
+        fragment.setCallBack(callback);
+        // 绑定到 Activity 上面
+        fragment.attachActivity(activity);
+    }
+
+    /**
+     * 绑定Activity
+     */
+    private void attachActivity(FragmentActivity activity) {
+        activity.getSupportFragmentManager().beginTransaction().add(this, this.toString()).commitAllowingStateLoss();
+    }
+
+    /**
+     * 解绑Activity
+     */
+    private void detachActivity(FragmentActivity activity) {
+        activity.getSupportFragmentManager().beginTransaction().remove(this).commitAllowingStateLoss();
+    }
+
+    /**
+     * 设置权限监听回调监听
+     */
+    public void setCallBack(IPermissionCallback callback) {
+        mCallBack = callback;
+    }
+
+    /**
+     * 权限申请标记(防止系统杀死应用后重新触发请求的问题)
+     */
+    public void setRequestFlag(boolean flag) {
+        mRequestFlag = flag;
+    }
+
+
+    @SuppressLint("SourceLockedOrientationActivity")
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        FragmentActivity activity = getActivity();
+        if (activity == null) {
+            return;
+        }
+        // 如果当前没有锁定屏幕方向就获取当前屏幕方向并进行锁定
+        mScreenOrientation = activity.getRequestedOrientation();
+        if (mScreenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+            return;
+        }
+
+        try {
+            // 兼容问题:在 Android 8.0 的手机上可以固定 Activity 的方向,但是这个 Activity 不能是透明的,否则就会抛出异常
+            // 复现场景:只需要给 Activity 主题设置 <item name="android:windowIsTranslucent">true</item> 属性即可
+            switch (activity.getResources().getConfiguration().orientation) {
+                case Configuration.ORIENTATION_LANDSCAPE:
+                    activity.setRequestedOrientation(PermissionUtils.isActivityReverse(activity) ?
+                            ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE :
+                            ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+                    break;
+                case Configuration.ORIENTATION_PORTRAIT:
+                default:
+                    activity.setRequestedOrientation(PermissionUtils.isActivityReverse(activity) ?
+                            ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT :
+                            ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+                    break;
+            }
+        } catch (IllegalStateException e) {
+            // java.lang.IllegalStateException: Only fullscreen activities can request orientation
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        FragmentActivity activity = getActivity();
+        if (activity == null || mScreenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+            return;
+        }
+        // 为什么这里不用跟上面一样 try catch ?因为这里是把 Activity 方向取消固定,只有设置横屏或竖屏的时候才可能触发 crash
+        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        // 取消引用监听器,避免内存泄漏
+        mCallBack = null;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        // 如果当前 Fragment 是通过系统重启应用触发的,则不进行权限申请
+        if (!mRequestFlag) {
+            detachActivity(getActivity());
+            return;
+        }
+
+        // 如果在 Activity 不可见的状态下添加 Fragment 并且去申请权限会导致授权对话框显示不出来
+        // 所以必须要在 Fragment 的 onResume 来申请权限,这样就可以保证应用回到前台的时候才去申请权限
+        if (mSpecialRequest) {
+            return;
+        }
+
+        mSpecialRequest = true;
+        requestSpecialPermission();
+    }
+
+    /**
+     * 申请特殊权限
+     */
+    public void requestSpecialPermission() {
+        Bundle arguments = getArguments();
+        FragmentActivity activity = getActivity();
+        if (arguments == null || activity == null) {
+            return;
+        }
+
+        List<String> permissions = arguments.getStringArrayList(REQUEST_PERMISSIONS);
+
+        // 是否需要申请特殊权限
+        boolean requestSpecialPermission = false;
+
+        // 判断当前是否包含特殊权限
+        for (String permission : permissions) {
+            if (PermissionUtils.isSpecialPermission(permission)) {
+                if (PermissionUtils.isGrantedPermission(activity, permission)) {
+                    // 已经授予过了,可以跳过
+                    continue;
+                }
+                if (Permission.MANAGE_EXTERNAL_STORAGE.equals(permission) && !PermissionUtils.isAndroid11()) {
+                    // 当前必须是 Android 11 及以上版本,因为在旧版本上是拿旧权限做的判断
+                    continue;
+                }
+                // 跳转到特殊权限授权页面
+                startActivityForResult(PermissionSettingPage.getSmartPermissionIntent(activity,
+                        PermissionUtils.asArrayList(permission)), getArguments().getInt(REQUEST_CODE));
+                requestSpecialPermission = true;
+            }
+        }
+
+        if (requestSpecialPermission) {
+            return;
+        }
+        // 如果没有跳转到特殊权限授权页面,就直接申请危险权限
+        requestDangerousPermission();
+    }
+
+    /**
+     * 申请危险权限
+     */
+    public void requestDangerousPermission() {
+        FragmentActivity activity = getActivity();
+        Bundle arguments = getArguments();
+        if (activity == null || arguments == null) {
+            return;
+        }
+
+        final int requestCode = arguments.getInt(REQUEST_CODE);
+
+        final ArrayList<String> allPermissions = arguments.getStringArrayList(REQUEST_PERMISSIONS);
+        if (allPermissions == null || allPermissions.size() == 0) {
+            return;
+        }
+
+        ArrayList<String> locationPermission = null;
+        // Android 10 定位策略发生改变,申请后台定位权限的前提是要有前台定位权限(授予了精确或者模糊任一权限)
+        if (PermissionUtils.isAndroid10() && allPermissions.contains(Permission.ACCESS_BACKGROUND_LOCATION)) {
+            locationPermission = new ArrayList<>();
+            if (allPermissions.contains(Permission.ACCESS_COARSE_LOCATION)) {
+                locationPermission.add(Permission.ACCESS_COARSE_LOCATION);
+            }
+
+            if (allPermissions.contains(Permission.ACCESS_FINE_LOCATION)) {
+                locationPermission.add(Permission.ACCESS_FINE_LOCATION);
+            }
+        }
+
+        if (!PermissionUtils.isAndroid10() || locationPermission == null || locationPermission.isEmpty()) {
+            requestPermissions(allPermissions.toArray(new String[allPermissions.size() - 1]), getArguments().getInt(REQUEST_CODE));
+            return;
+        }
+
+        // 在 Android 10 的机型上,需要先申请前台定位权限,再申请后台定位权限
+        PermissionKitFragment.beginRequest(activity, locationPermission, new IPermissionCallback() {
+            @Override
+            public void onGranted(List<String> permissions, boolean all) {
+                if (!all || !isAdded()) {
+                    return;
+                }
+
+                // 前台定位权限授予了,现在申请后台定位权限
+                PermissionKitFragment.beginRequest(activity, PermissionUtils.asArrayList(Permission.ACCESS_BACKGROUND_LOCATION), new IPermissionCallback() {
+
+                    @Override
+                    public void onGranted(List<String> permissions, boolean all) {
+                        if (!all || !isAdded()) {
+                            return;
+                        }
+
+                        // 前台定位权限和后台定位权限都授予了
+                        int[] grantResults = new int[allPermissions.size()];
+                        Arrays.fill(grantResults, PackageManager.PERMISSION_GRANTED);
+                        onRequestPermissionsResult(requestCode, allPermissions.toArray(new String[0]), grantResults);
+                    }
+
+                    @Override
+                    public void onDenied(List<String> permissions, boolean never) {
+                        if (!isAdded()) {
+                            return;
+                        }
+
+                        // 后台定位授权失败,但是前台定位权限已经授予了
+                        int[] grantResults = new int[allPermissions.size()];
+                        for (int i = 0; i < allPermissions.size(); i++) {
+                            grantResults[i] = Permission.ACCESS_BACKGROUND_LOCATION.equals(allPermissions.get(i)) ?
+                                    PackageManager.PERMISSION_DENIED : PackageManager.PERMISSION_GRANTED;
+                        }
+                        onRequestPermissionsResult(requestCode, allPermissions.toArray(new String[0]), grantResults);
+                    }
+
+                    @Override
+                    public void onProxyFinish() {
+
+                    }
+                });
+            }
+
+            @Override
+            public void onDenied(List<String> permissions, boolean never) {
+                if (!isAdded()) {
+                    return;
+                }
+
+                // 前台定位授权失败,并且无法申请后台定位权限
+                int[] grantResults = new int[allPermissions.size()];
+                Arrays.fill(grantResults, PackageManager.PERMISSION_DENIED);
+                onRequestPermissionsResult(requestCode, allPermissions.toArray(new String[0]), grantResults);
+            }
+
+            @Override
+            public void onProxyFinish() {
+
+            }
+        });
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+        Bundle arguments = getArguments();
+        FragmentActivity activity = getActivity();
+        if (activity == null || arguments == null || mCallBack == null || requestCode != arguments.getInt(REQUEST_CODE)) {
+            return;
+        }
+
+        IPermissionCallback callback = mCallBack;
+        mCallBack = null;
+
+        // 优化权限回调结果
+        PermissionUtils.optimizePermissionResults(activity, permissions, grantResults);
+
+        // 释放对这个请求码的占用
+        REQUEST_CODE_ARRAY.remove(requestCode);
+        // 将 Fragment 从 Activity 移除
+        detachActivity(activity);
+
+        // 获取已授予的权限
+        List<String> grantedPermission = PermissionUtils.getGrantedPermissions(permissions, grantResults);
+
+        // 如果请求成功的权限集合大小和请求的数组一样大时证明权限已经全部授予
+        if (grantedPermission.size() != permissions.length) {
+            callback.onGranted(grantedPermission, true);
+            return;
+        }
+
+        // 获取被拒绝的权限
+        List<String> deniedPermission = PermissionUtils.getDeniedPermissions(permissions, grantResults);
+
+        callback.onDenied(deniedPermission, PermissionUtils.isPermissionPermanentDenied(activity, deniedPermission));
+
+        // 证明还有一部分权限被成功授予,回调成功接口
+        if (!grantedPermission.isEmpty()) {
+            callback.onGranted(grantedPermission, false);
+        }
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        FragmentActivity activity = getActivity();
+        Bundle arguments = getArguments();
+        if (activity == null || arguments == null || mDangerousRequest || requestCode != arguments.getInt(REQUEST_CODE)) {
+            return;
+        }
+
+        mDangerousRequest = true;
+        // 需要延迟执行,不然有些华为机型授权了但是获取不到权限
+        activity.getWindow().getDecorView().postDelayed(this, 300);
+    }
+
+    @Override
+    public void run() {
+        // 如果用户离开太久,会导致 Activity 被回收掉
+        // 所以这里要判断当前 Fragment 是否有被添加到 Activity
+        // 可在开发者模式中开启不保留活动来复现这个 Bug
+        if (!isAdded()) {
+            return;
+        }
+        // 请求其他危险权限
+        requestDangerousPermission();
+    }
+}

+ 166 - 0
library_support/src/main/java/cn/yyxx/support/permission/PermissionSettingPage.java

@@ -0,0 +1,166 @@
+package cn.yyxx.support.permission;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.provider.Settings;
+
+import java.util.List;
+
+/**
+ *    author : Android 轮子哥
+ *    github : https://github.com/getActivity/XXPermissions
+ *    time   : 2020/08/18
+ *    desc   : 权限设置页
+ */
+final class PermissionSettingPage {
+
+    /**
+     * 根据传入的权限自动选择最合适的权限设置页
+     *
+     * @param permissions                 请求失败的权限
+     */
+    static Intent getSmartPermissionIntent(Context context, List<String> permissions) {
+        // 如果失败的权限里面不包含特殊权限
+        if (permissions == null || permissions.isEmpty() || !PermissionUtils.containsSpecialPermission(permissions)) {
+            return getApplicationDetailsIntent(context);
+        }
+
+        if (PermissionUtils.isAndroid11() && permissions.size() == 3 &&
+                (permissions.contains(Permission.MANAGE_EXTERNAL_STORAGE) &&
+                        permissions.contains(Permission.READ_EXTERNAL_STORAGE) &&
+                        permissions.contains(Permission.WRITE_EXTERNAL_STORAGE))) {
+            return getStoragePermissionIntent(context);
+        }
+
+        // 如果当前只有一个权限被拒绝了
+        if (permissions.size() == 1) {
+
+            String permission = permissions.get(0);
+            if (Permission.MANAGE_EXTERNAL_STORAGE.equals(permission)) {
+                return getStoragePermissionIntent(context);
+            }
+
+            if (Permission.REQUEST_INSTALL_PACKAGES.equals(permission)) {
+                return getInstallPermissionIntent(context);
+            }
+
+            if (Permission.SYSTEM_ALERT_WINDOW.equals(permission)) {
+                return getWindowPermissionIntent(context);
+            }
+
+            if (Permission.NOTIFICATION_SERVICE.equals(permission)) {
+                return getNotifyPermissionIntent(context);
+            }
+
+            if (Permission.WRITE_SETTINGS.equals(permission)) {
+                return getSettingPermissionIntent(context);
+            }
+        }
+
+        return getApplicationDetailsIntent(context);
+    }
+
+    /**
+     * 获取应用详情界面意图
+     */
+    static Intent getApplicationDetailsIntent(Context context) {
+        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+        intent.setData(getPackageNameUri(context));
+        return intent;
+    }
+
+    /**
+     * 获取安装权限设置界面意图
+     */
+    static Intent getInstallPermissionIntent(Context context) {
+        Intent intent = null;
+        if (PermissionUtils.isAndroid8()) {
+            intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
+            intent.setData(getPackageNameUri(context));
+        }
+        if (intent == null || !areActivityIntent(context, intent)) {
+            intent = getApplicationDetailsIntent(context);
+        }
+        return intent;
+    }
+
+    /**
+     * 获取悬浮窗权限设置界面意图
+     */
+    static Intent getWindowPermissionIntent(Context context) {
+        Intent intent = null;
+        if (PermissionUtils.isAndroid6()) {
+            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(getPackageNameUri(context));
+        }
+
+        if (intent == null || !areActivityIntent(context, intent)) {
+            intent = getApplicationDetailsIntent(context);
+        }
+        return intent;
+    }
+
+    /**
+     * 获取通知栏权限设置界面意图
+     */
+    static Intent getNotifyPermissionIntent(Context context) {
+        Intent intent = null;
+        if (PermissionUtils.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);
+        }
+        if (intent == null || !areActivityIntent(context, intent)) {
+            intent = getApplicationDetailsIntent(context);
+        }
+        return intent;
+    }
+
+    /**
+     * 获取系统设置权限界面意图
+     */
+    static Intent getSettingPermissionIntent(Context context) {
+        Intent intent = null;
+        if (PermissionUtils.isAndroid6()) {
+            intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
+            intent.setData(getPackageNameUri(context));
+        }
+        if (intent == null || !areActivityIntent(context, intent)) {
+            intent = getApplicationDetailsIntent(context);
+        }
+        return intent;
+    }
+
+    /**
+     * 获取存储权限设置界面意图
+     */
+    static Intent getStoragePermissionIntent(Context context) {
+        Intent intent = null;
+        if (PermissionUtils.isAndroid11()) {
+            intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
+            intent.setData(getPackageNameUri(context));
+        }
+        if (intent == null || !areActivityIntent(context, intent)) {
+            intent = getApplicationDetailsIntent(context);
+        }
+        return intent;
+    }
+
+    /**
+     * 判断这个意图的 Activity 是否存在
+     */
+    private static boolean areActivityIntent(Context context, Intent intent) {
+        return !context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).isEmpty();
+    }
+
+    /**
+     * 获取包名 Uri 对象
+     */
+    private static Uri getPackageNameUri(Context context) {
+        return Uri.parse("package:" + context.getPackageName());
+    }
+}

+ 679 - 0
library_support/src/main/java/cn/yyxx/support/permission/PermissionUtils.java

@@ -0,0 +1,679 @@
+package cn.yyxx.support.permission;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.AppOpsManager;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.XmlResourceParser;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.Settings;
+import android.support.v4.app.FragmentActivity;
+import android.view.Surface;
+
+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;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * author : Android 轮子哥
+ * github : https://github.com/getActivity/XXPermissions
+ * time   : 2018/06/15
+ * desc   : 权限相关工具类
+ */
+final class PermissionUtils {
+
+    /**
+     * Android 命名空间
+     */
+    static final String ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android";
+
+    /**
+     * 是否是 Android 12 及以上版本
+     */
+    @SuppressWarnings("all")
+    static boolean isAndroid12() {
+//        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
+        return Build.VERSION.SDK_INT >= 31;
+    }
+
+    /**
+     * 是否是 Android 11 及以上版本
+     */
+    static boolean isAndroid11() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
+    }
+
+    /**
+     * 是否是 Android 10 及以上版本
+     */
+    static boolean isAndroid10() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
+    }
+
+    /**
+     * 是否是 Android 9.0 及以上版本
+     */
+    @SuppressWarnings("all")
+    static boolean isAndroid9() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
+    }
+
+    /**
+     * 是否是 Android 8.0 及以上版本
+     */
+    static boolean isAndroid8() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
+    }
+
+    /**
+     * 是否是 Android 7.0 及以上版本
+     */
+    static boolean isAndroid7() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
+    }
+
+    /**
+     * 是否是 Android 6.0 及以上版本
+     */
+    static boolean isAndroid6() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
+    }
+
+    /**
+     * 返回应用程序在清单文件中注册的权限
+     */
+    static HashMap<String, Integer> getManifestPermissions(Context context) {
+        HashMap<String, Integer> manifestPermissions = new HashMap<>();
+
+        XmlResourceParser parser = PermissionUtils.parseAndroidManifest(context);
+
+        if (parser != null) {
+            try {
+
+                do {
+                    // 当前节点必须为标签头部
+                    if (parser.getEventType() != XmlResourceParser.START_TAG) {
+                        continue;
+                    }
+
+                    // 当前标签必须为 uses-permission
+                    if (!"uses-permission".equals(parser.getName())) {
+                        continue;
+                    }
+
+                    manifestPermissions.put(parser.getAttributeValue(ANDROID_NAMESPACE, "name"),
+                            parser.getAttributeIntValue(ANDROID_NAMESPACE, "maxSdkVersion", Integer.MAX_VALUE));
+
+                } while (parser.next() != XmlResourceParser.END_DOCUMENT);
+
+            } catch (IOException | XmlPullParserException e) {
+                e.printStackTrace();
+            } finally {
+                parser.close();
+            }
+        }
+
+        if (manifestPermissions.isEmpty()) {
+            try {
+                // 当清单文件没有注册任何权限的时候,那么这个数组对象就是空的
+                // https://github.com/getActivity/XXPermissions/issues/35
+                String[] requestedPermissions = context.getPackageManager().getPackageInfo(
+                        context.getPackageName(), PackageManager.GET_PERMISSIONS).requestedPermissions;
+                if (requestedPermissions != null) {
+                    for (String permission : requestedPermissions) {
+                        manifestPermissions.put(permission, Integer.MAX_VALUE);
+                    }
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                e.printStackTrace();
+            }
+        }
+
+        return manifestPermissions;
+    }
+
+    /**
+     * 是否有存储权限
+     */
+    static boolean isGrantedStoragePermission(Context context) {
+        if (isAndroid11()) {
+            return Environment.isExternalStorageManager();
+        }
+        return isGrantedPermissions(context, asArrayList(Permission.Group.STORAGE));
+    }
+
+    /**
+     * 是否有安装权限
+     */
+    static boolean isGrantedInstallPermission(Context context) {
+        if (isAndroid8()) {
+            return context.getPackageManager().canRequestPackageInstalls();
+        }
+        return true;
+    }
+
+    /**
+     * 是否有悬浮窗权限
+     */
+    static boolean isGrantedWindowPermission(Context context) {
+        if (isAndroid6()) {
+            return Settings.canDrawOverlays(context);
+        }
+        return true;
+    }
+
+    /**
+     * 是否有通知栏权限
+     */
+    @SuppressWarnings("ConstantConditions")
+    static boolean isGrantedNotifyPermission(Context context) {
+        if (isAndroid7()) {
+            return context.getSystemService(NotificationManager.class).areNotificationsEnabled();
+        }
+
+        if (isAndroid6()) {
+            // 参考 Support 库中的方法: NotificationManagerCompat.from(context).areNotificationsEnabled()
+            AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+            try {
+                Method method = appOps.getClass().getMethod("checkOpNoThrow",
+                        Integer.TYPE, Integer.TYPE, String.class);
+                Field field = appOps.getClass().getDeclaredField("OP_POST_NOTIFICATION");
+                int value = (int) field.get(Integer.class);
+                return ((int) method.invoke(appOps, value, context.getApplicationInfo().uid,
+                        context.getPackageName())) == AppOpsManager.MODE_ALLOWED;
+            } catch (NoSuchMethodException | NoSuchFieldException | InvocationTargetException |
+                    IllegalAccessException | RuntimeException e) {
+                e.printStackTrace();
+                return true;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * 是否有系统设置权限
+     */
+    static boolean isGrantedSettingPermission(Context context) {
+        if (isAndroid6()) {
+            return Settings.System.canWrite(context);
+        }
+        return true;
+    }
+
+    /**
+     * 判断某个权限集合是否包含特殊权限
+     */
+    static boolean containsSpecialPermission(List<String> permissions) {
+        if (permissions == null || permissions.isEmpty()) {
+            return false;
+        }
+
+        for (String permission : permissions) {
+            if (isSpecialPermission(permission)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 判断某个权限是否是特殊权限
+     */
+    static boolean isSpecialPermission(String permission) {
+        return Permission.MANAGE_EXTERNAL_STORAGE.equals(permission) ||
+                Permission.REQUEST_INSTALL_PACKAGES.equals(permission) ||
+                Permission.SYSTEM_ALERT_WINDOW.equals(permission) ||
+                Permission.NOTIFICATION_SERVICE.equals(permission) ||
+                Permission.WRITE_SETTINGS.equals(permission);
+    }
+
+    /**
+     * 判断某些权限是否全部被授予
+     */
+    static boolean isGrantedPermissions(Context context, List<String> permissions) {
+        // 如果是安卓 6.0 以下版本就直接返回 true
+        if (!isAndroid6()) {
+            return true;
+        }
+
+        if (permissions == null || permissions.isEmpty()) {
+            return false;
+        }
+
+        for (String permission : permissions) {
+            if (!isGrantedPermission(context, permission)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * 获取没有授予的权限
+     */
+    static List<String> getDeniedPermissions(Context context, List<String> permissions) {
+        List<String> deniedPermission = new ArrayList<>(permissions.size());
+
+        // 如果是安卓 6.0 以下版本就默认授予
+        if (!isAndroid6()) {
+            return deniedPermission;
+        }
+
+        for (String permission : permissions) {
+            if (!isGrantedPermission(context, permission)) {
+                deniedPermission.add(permission);
+            }
+        }
+        return deniedPermission;
+    }
+
+    /**
+     * 判断某个权限是否授予
+     */
+    static boolean isGrantedPermission(Context context, String permission) {
+        // 如果是安卓 6.0 以下版本就默认授予
+        if (!isAndroid6()) {
+            return true;
+        }
+
+        // 检测存储权限
+        if (Permission.MANAGE_EXTERNAL_STORAGE.equals(permission)) {
+            return isGrantedStoragePermission(context);
+        }
+
+        // 检测安装权限
+        if (Permission.REQUEST_INSTALL_PACKAGES.equals(permission)) {
+            return isGrantedInstallPermission(context);
+        }
+
+        // 检测悬浮窗权限
+        if (Permission.SYSTEM_ALERT_WINDOW.equals(permission)) {
+            return isGrantedWindowPermission(context);
+        }
+
+        // 检测通知栏权限
+        if (Permission.NOTIFICATION_SERVICE.equals(permission)) {
+            return isGrantedNotifyPermission(context);
+        }
+
+        // 检测系统权限
+        if (Permission.WRITE_SETTINGS.equals(permission)) {
+            return isGrantedSettingPermission(context);
+        }
+
+        // 检测 Android 12 的三个新权限
+        if (!isAndroid12()) {
+
+            if (Permission.BLUETOOTH_SCAN.equals(permission)) {
+                return context.checkSelfPermission(Permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
+            }
+
+            if (Permission.BLUETOOTH_CONNECT.equals(permission) ||
+                    Permission.BLUETOOTH_ADVERTISE.equals(permission)) {
+                return true;
+            }
+        }
+
+        // 检测 Android 10 的三个新权限
+        if (!isAndroid10()) {
+
+            if (Permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) {
+                return context.checkSelfPermission(Permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
+            }
+
+            if (Permission.ACTIVITY_RECOGNITION.equals(permission)) {
+                return context.checkSelfPermission(Permission.BODY_SENSORS) == PackageManager.PERMISSION_GRANTED;
+            }
+
+            if (Permission.ACCESS_MEDIA_LOCATION.equals(permission)) {
+                return true;
+            }
+        }
+
+        // 检测 Android 9.0 的一个新权限
+        if (!isAndroid9()) {
+
+            if (Permission.ACCEPT_HANDOVER.equals(permission)) {
+                return true;
+            }
+        }
+
+        // 检测 Android 8.0 的两个新权限
+        if (!isAndroid8()) {
+
+            if (Permission.ANSWER_PHONE_CALLS.equals(permission)) {
+                return true;
+            }
+
+            if (Permission.READ_PHONE_NUMBERS.equals(permission)) {
+                return context.checkSelfPermission(Permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED;
+            }
+        }
+
+        return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    /**
+     * 优化权限回调结果
+     */
+    static void optimizePermissionResults(Activity activity, String[] permissions, int[] grantResults) {
+        for (int i = 0; i < permissions.length; i++) {
+
+            boolean recheck = false;
+
+            String permission = permissions[i];
+
+            // 如果这个权限是特殊权限,那么就重新进行权限检测
+            if (PermissionUtils.isSpecialPermission(permission)) {
+                recheck = true;
+            }
+
+            // 重新检查 Android 12 的三个新权限
+            if (!PermissionUtils.isAndroid12() &&
+                    (Permission.BLUETOOTH_SCAN.equals(permission) ||
+                            Permission.BLUETOOTH_CONNECT.equals(permission) ||
+                            Permission.BLUETOOTH_ADVERTISE.equals(permission))) {
+                recheck = true;
+            }
+
+            // 重新检查 Android 10.0 的三个新权限
+            if (!PermissionUtils.isAndroid10() &&
+                    (Permission.ACCESS_BACKGROUND_LOCATION.equals(permission) ||
+                            Permission.ACTIVITY_RECOGNITION.equals(permission) ||
+                            Permission.ACCESS_MEDIA_LOCATION.equals(permission))) {
+                recheck = true;
+            }
+
+            // 重新检查 Android 9.0 的一个新权限
+            if (!PermissionUtils.isAndroid9() &&
+                    Permission.ACCEPT_HANDOVER.equals(permission)) {
+                recheck = true;
+            }
+
+            // 重新检查 Android 8.0 的两个新权限
+            if (!PermissionUtils.isAndroid8() &&
+                    (Permission.ANSWER_PHONE_CALLS.equals(permission) ||
+                            Permission.READ_PHONE_NUMBERS.equals(permission))) {
+                recheck = true;
+            }
+
+            if (recheck) {
+                grantResults[i] = PermissionUtils.isGrantedPermission(activity, permission) ?
+                        PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
+            }
+        }
+    }
+
+    /**
+     * 在权限组中检查是否有某个权限是否被永久拒绝
+     *
+     * @param activity    Activity对象
+     * @param permissions 请求的权限
+     */
+    static boolean isPermissionPermanentDenied(Activity activity, List<String> permissions) {
+        for (String permission : permissions) {
+            if (isPermissionPermanentDenied(activity, permission)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 判断某个权限是否被永久拒绝
+     *
+     * @param activity   Activity对象
+     * @param permission 请求的权限
+     */
+    static boolean isPermissionPermanentDenied(Activity activity, String permission) {
+        if (!isAndroid6()) {
+            return false;
+        }
+
+        // 特殊权限不算,本身申请方式和危险权限申请方式不同,因为没有永久拒绝的选项,所以这里返回 false
+        if (isSpecialPermission(permission)) {
+            return false;
+        }
+
+        // 检测 Android 12 的三个新权限
+        if (!isAndroid12()) {
+
+            if (Permission.BLUETOOTH_SCAN.equals(permission)) {
+                return !isGrantedPermission(activity, Permission.ACCESS_COARSE_LOCATION) &&
+                        !activity.shouldShowRequestPermissionRationale(Permission.ACCESS_COARSE_LOCATION);
+            }
+
+            if (Permission.BLUETOOTH_CONNECT.equals(permission) ||
+                    Permission.BLUETOOTH_ADVERTISE.equals(permission)) {
+                return false;
+            }
+        }
+
+        if (isAndroid10()) {
+
+            // 重新检测后台定位权限是否永久拒绝
+            if (Permission.ACCESS_BACKGROUND_LOCATION.equals(permission) &&
+                    !isGrantedPermission(activity, Permission.ACCESS_BACKGROUND_LOCATION) &&
+                    !isGrantedPermission(activity, Permission.ACCESS_FINE_LOCATION)) {
+                return !activity.shouldShowRequestPermissionRationale(Permission.ACCESS_FINE_LOCATION);
+            }
+        }
+
+        // 检测 Android 10 的三个新权限
+        if (!isAndroid10()) {
+
+            if (Permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) {
+                return !isGrantedPermission(activity, Permission.ACCESS_FINE_LOCATION) &&
+                        !activity.shouldShowRequestPermissionRationale(Permission.ACCESS_FINE_LOCATION);
+            }
+
+            if (Permission.ACTIVITY_RECOGNITION.equals(permission)) {
+                return !isGrantedPermission(activity, Permission.BODY_SENSORS) &&
+                        !activity.shouldShowRequestPermissionRationale(Permission.BODY_SENSORS);
+            }
+
+            if (Permission.ACCESS_MEDIA_LOCATION.equals(permission)) {
+                return false;
+            }
+        }
+
+        // 检测 Android 9.0 的一个新权限
+        if (!isAndroid9()) {
+
+            if (Permission.ACCEPT_HANDOVER.equals(permission)) {
+                return false;
+            }
+        }
+
+        // 检测 Android 8.0 的两个新权限
+        if (!isAndroid8()) {
+
+            if (Permission.ANSWER_PHONE_CALLS.equals(permission)) {
+                return false;
+            }
+
+            if (Permission.READ_PHONE_NUMBERS.equals(permission)) {
+                return !isGrantedPermission(activity, Permission.READ_PHONE_STATE) &&
+                        !activity.shouldShowRequestPermissionRationale(Permission.READ_PHONE_STATE);
+            }
+        }
+
+        return !isGrantedPermission(activity, permission) &&
+                !activity.shouldShowRequestPermissionRationale(permission);
+    }
+
+    /**
+     * 获取没有授予的权限
+     *
+     * @param permissions  需要请求的权限组
+     * @param grantResults 允许结果组
+     */
+    static List<String> getDeniedPermissions(String[] permissions, int[] grantResults) {
+        List<String> deniedPermissions = new ArrayList<>();
+        for (int i = 0; i < grantResults.length; i++) {
+            // 把没有授予过的权限加入到集合中
+            if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
+                deniedPermissions.add(permissions[i]);
+            }
+        }
+        return deniedPermissions;
+    }
+
+    /**
+     * 获取已授予的权限
+     *
+     * @param permissions  需要请求的权限组
+     * @param grantResults 允许结果组
+     */
+    static List<String> getGrantedPermissions(String[] permissions, int[] grantResults) {
+        List<String> grantedPermissions = new ArrayList<>();
+        for (int i = 0; i < grantResults.length; i++) {
+            // 把授予过的权限加入到集合中
+            if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
+                grantedPermissions.add(permissions[i]);
+            }
+        }
+        return grantedPermissions;
+    }
+
+    /**
+     * 将数组转换成 ArrayList
+     * <p>
+     * 这里解释一下为什么不用 Arrays.asList
+     * 第一是返回的类型不是 java.util.ArrayList 而是 java.util.Arrays.ArrayList
+     * 第二是返回的 ArrayList 对象是只读的,也就是不能添加任何元素,否则会抛异常
+     */
+    @SuppressWarnings("all")
+    static <T> ArrayList<T> asArrayList(T... array) {
+        if (array == null || array.length == 0) {
+            return null;
+        }
+        ArrayList<T> list = new ArrayList<>(array.length);
+        for (T t : array) {
+            list.add(t);
+        }
+        return list;
+    }
+
+    @SafeVarargs
+    static <T> ArrayList<T> asArrayLists(T[]... arrays) {
+        ArrayList<T> list = new ArrayList<>();
+        if (arrays == null || arrays.length == 0) {
+            return list;
+        }
+        for (T[] ts : arrays) {
+            list.addAll(asArrayList(ts));
+        }
+        return list;
+    }
+
+    /**
+     * 寻找上下文中的 Activity 对象
+     */
+    static FragmentActivity findActivity(Context context) {
+        do {
+            if (context instanceof FragmentActivity) {
+                return (FragmentActivity) context;
+            } else if (context instanceof ContextWrapper) {
+                context = ((ContextWrapper) context).getBaseContext();
+            } else {
+                return null;
+            }
+        } while (context != null);
+        return null;
+    }
+
+    /**
+     * 获取当前应用 Apk 在 AssetManager 中的 Cookie
+     */
+    @SuppressWarnings("JavaReflectionMemberAccess")
+    @SuppressLint("PrivateApi")
+    static Integer findApkPathCookie(Context context) {
+        AssetManager assets = context.getAssets();
+        String path = context.getApplicationInfo().sourceDir;
+        try {
+            // 为什么不直接通过反射 AssetManager.findCookieForPath 方法来判断?因为这个 API 属于反射黑名单,反射执行不了
+            // 为什么不直接通过反射 AssetManager.addAssetPathInternal 这个非隐藏的方法来判断?因为这个也反射不了
+            Method method = assets.getClass().getDeclaredMethod("addOverlayPath", String.class);
+            // Android 9.0 以下获取到的结果会为零
+            // Android 9.0 及以上获取到的结果会大于零
+            return (Integer) method.invoke(assets, path);
+        } catch (Exception e) {
+            // NoSuchMethodException
+            // IllegalAccessException
+            // InvocationTargetException
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 解析清单文件
+     */
+    static XmlResourceParser parseAndroidManifest(Context context) {
+        Integer cookie = PermissionUtils.findApkPathCookie(context);
+        if (cookie == null) {
+            // 如果 cookie 为 null,证明获取失败,直接 return
+            return null;
+        }
+
+        try {
+            return context.getAssets().openXmlResourceParser(cookie, "AndroidManifest.xml");
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 判断是否适配了分区存储
+     */
+    static boolean isScopedStorage(Context context) {
+        try {
+            String metaKey = "ScopedStorage";
+            Bundle metaData = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA).metaData;
+            if (metaData != null && metaData.containsKey(metaKey)) {
+                return Boolean.parseBoolean(String.valueOf(metaData.get(metaKey)));
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return false;
+    }
+
+    /**
+     * 判断 Activity 是否反方向旋转了
+     */
+    static boolean isActivityReverse(Activity activity) {
+        // 获取 Activity 旋转的角度
+        int activityRotation;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            activityRotation = activity.getDisplay().getRotation();
+        } else {
+            activityRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
+        }
+        switch (activityRotation) {
+            case Surface.ROTATION_180:
+            case Surface.ROTATION_270:
+                return true;
+            case Surface.ROTATION_0:
+            case Surface.ROTATION_90:
+            default:
+                return false;
+        }
+    }
+}

+ 4 - 4
library_volley/build.gradle

@@ -3,12 +3,12 @@ plugins {
 }
 
 android {
-    compileSdkVersion COMPILE_SDK_VERSION
-    buildToolsVersion BUILD_TOOLS_VERSION
+    compileSdkVersion rootProject.ext.android.compileSdkVersion
+    buildToolsVersion rootProject.ext.android.buildToolsVersion
 
     defaultConfig {
-        minSdkVersion MIN_SDK_VERSION
-        targetSdkVersion TARGET_SDK_VERSION
+        minSdkVersion rootProject.ext.android.minSdkVersion
+        targetSdkVersion rootProject.ext.android.targetSdkVersion
     }
 
     buildTypes {

+ 6 - 6
library_volleyx/build.gradle

@@ -3,12 +3,12 @@ plugins {
 }
 
 android {
-    compileSdkVersion COMPILE_SDK_VERSION
-    buildToolsVersion BUILD_TOOLS_VERSION
+    compileSdkVersion rootProject.ext.android.compileSdkVersion
+    buildToolsVersion rootProject.ext.android.buildToolsVersion
 
     defaultConfig {
-        minSdkVersion MIN_SDK_VERSION
-        targetSdkVersion TARGET_SDK_VERSION
+        minSdkVersion rootProject.ext.android.minSdkVersion
+        targetSdkVersion rootProject.ext.android.targetSdkVersion
     }
 
     buildTypes {
@@ -41,8 +41,8 @@ android {
 }
 
 dependencies {
-    implementation "org.chromium.net:cronet-embedded:76.3809.111"
-    implementation 'androidx.core:core:1.5.0'
+    implementation rootProject.ext.other['cronet']
+    implementation rootProject.ext.ktx['core']
 }
 
 apply from: 'buildJar.gradle'