diff --git a/README.md b/README.md index 3c1b382..de271db 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,40 @@ ![](./images/g_permission_instruction.png) +## Import +第一步,在项目根目录下的`build.gradle`文件中引入`Jitpack`的库路径: +```groovy +allprojects { + repositories { + // Your codes ... + maven { url 'https://jitpack.io' } + } +} +``` +第二步,在需要依赖`GPermission`的`Module`下的`build.gradle`文件中添加依赖: +```groovy +dependencies { + // Your codes ... + // GPermission + implementation "com.github.ITGungnir:GPermission:$permission_version" +} +``` + ## Usage 第一步,在`AndroidManifest.xml`文件中添加要请求的权限: ```xml + + ``` 第二步,在适当的地方请求权限: ```kotlin +// with()方法中的参数可以是 FragmentActivity或Fragment的子类 GPermission.with(this) // 请求权限成功时的回调 .onGranted { @@ -31,9 +53,20 @@ GPermission.with(this) // 开始请求权限 .request( Manifest.permission.WRITE_EXTERNAL_STORAGE to "文件读写", - Manifest.permission.READ_PHONE_STATE to "获取手机状态" + Manifest.permission.READ_PHONE_STATE to "获取手机状态", + Manifest.permission.CAMERA to "相机", + Manifest.permission.RECORD_AUDIO to "麦克风录音" ) ``` +`GPermission`还提供了验证权限是否已获取的方法: +```kotlin +tvResult.text = GPermission.with(this).allGranted( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.CAMERA, + Manifest.permission.RECORD_AUDIO +).toString() +``` ## License ```text diff --git a/app/build.gradle b/app/build.gradle index 5ce451f..57bc206 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,13 +28,8 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "org.jetbrains.kotlin:kotlin-android-extensions-runtime:$kotlin_version" - implementation "org.jetbrains.anko:anko-sdk25:$anko_version" - implementation "org.jetbrains.anko:anko-appcompat-v7:$anko_version" // Support implementation "androidx.appcompat:appcompat:$appcompat_version" - implementation "com.google.android.material:material:$material_version" - implementation "androidx.constraintlayout:constraintlayout:$constraintlayout_version" // Tools debugImplementation "com.squareup.leakcanary:leakcanary-android:$leak_canary_version" debugImplementation "com.squareup.leakcanary:leakcanary-support-fragment:$leak_canary_version" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 35dbca7..801174c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,8 @@ + + - + android:layout_height="match_parent" + android:gravity="center" + android:orientation="vertical"> + android:textColor="@android:color/black" /> - \ No newline at end of file + \ No newline at end of file diff --git a/config.gradle b/config.gradle index e847b6e..2f9b550 100644 --- a/config.gradle +++ b/config.gradle @@ -6,15 +6,11 @@ ext { version_code = 1 version_name = '1.0.0' // Kotlin - kotlin_version = '1.3.31' - anko_version = '0.10.8' + kotlin_version = '1.3.40' // Support appcompat_version = '1.0.2' - material_version = '1.0.0' - constraintlayout_version = '1.1.3' // ReactiveX rxjava_version = '2.2.4' - rxpermissions_version = '0.10.2' // Tools jitpack_version = '2.1' leak_canary_version = '1.6.3' diff --git a/permission/build.gradle b/permission/build.gradle index 1c63af7..ce2293e 100644 --- a/permission/build.gradle +++ b/permission/build.gradle @@ -1,8 +1,8 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' -apply plugin: 'com.github.dcendents.android-maven' +apply plugin: 'com.github.dcendents.android-maven' group = 'com.github.ITGungnir' android { @@ -24,14 +24,8 @@ android { dependencies { // Kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "org.jetbrains.kotlin:kotlin-android-extensions-runtime:$kotlin_version" - implementation "org.jetbrains.anko:anko-sdk25:$anko_version" - implementation "org.jetbrains.anko:anko-appcompat-v7:$anko_version" // Support implementation "androidx.appcompat:appcompat:$appcompat_version" - implementation "com.google.android.material:material:$material_version" - implementation "androidx.constraintlayout:constraintlayout:$constraintlayout_version" // ReactiveX - api "io.reactivex.rxjava2:rxjava:$rxjava_version" - implementation "com.github.tbruyelle:rxpermissions:$rxpermissions_version" + implementation "io.reactivex.rxjava2:rxjava:$rxjava_version" } diff --git a/permission/src/main/java/my/itgungnir/permission/GPermission.kt b/permission/src/main/java/my/itgungnir/permission/GPermission.kt index e8bd376..73b33ff 100644 --- a/permission/src/main/java/my/itgungnir/permission/GPermission.kt +++ b/permission/src/main/java/my/itgungnir/permission/GPermission.kt @@ -6,22 +6,30 @@ import android.content.pm.PackageManager import android.net.Uri import android.provider.Settings import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity -import com.tbruyelle.rxpermissions2.RxPermissions +import androidx.lifecycle.ViewModelStoreOwner class GPermission private constructor() { private lateinit var context: FragmentActivity - private lateinit var permissionUtil: RxPermissions + private lateinit var permissionUtil: GPermissionProxy private var grantedCallback: (() -> Unit)? = null private var deniedCallback: (() -> Unit)? = null companion object { - fun with(activity: FragmentActivity) = GPermission().apply { - context = activity - permissionUtil = RxPermissions(activity) + fun with(component: ViewModelStoreOwner) = GPermission().apply { + permissionUtil = GPermissionProxy.with(component) + context = when (component) { + is FragmentActivity -> + component + is Fragment -> + component.activity ?: throw IllegalArgumentException("Fragment didn't attach to any Activity.") + else -> + throw IllegalArgumentException("GPermission requested from wrong component.") + } } } @@ -47,17 +55,19 @@ class GPermission private constructor() { } } - private fun lackOnes(vararg permissions: Pair): List { - return permissions.filter { granted(permission = it.first) } + fun allGranted(vararg permissions: String) = permissions.all { granted(it) } + + private fun lackedOnes(vararg permissions: Pair): List { + return permissions.filter { !granted(permission = it.first) } .map { it.second } } private fun granted(permission: String): Boolean = - ContextCompat.checkSelfPermission(context.applicationContext, permission) == PackageManager.PERMISSION_DENIED + ContextCompat.checkSelfPermission(context.applicationContext, permission) == PackageManager.PERMISSION_GRANTED private fun requestRepeatedly(vararg permissions: Pair) { GPermissionDialog.Builder() - .message("请允许系统获取${lackOnes(*permissions)}权限") + .message("请允许系统获取${lackedOnes(*permissions)}权限") .onConfirm { request(*permissions) } .onCancel { deniedCallback?.invoke() } .create() @@ -66,7 +76,7 @@ class GPermission private constructor() { private fun requestManually(vararg permissions: Pair) { GPermissionDialog.Builder() - .message("由于系统无法获取${lackOnes(*permissions)}权限,不能正常运行,请开启权限后再使用!") + .message("由于系统无法获取${lackedOnes(*permissions)}权限,不能正常运行,请开启权限后再使用!") .onConfirm { toSystemConfigPage() } .onCancel { deniedCallback?.invoke() } .create() diff --git a/permission/src/main/java/my/itgungnir/permission/GPermissionDialog.kt b/permission/src/main/java/my/itgungnir/permission/GPermissionDialog.kt index 32248c1..f07097e 100644 --- a/permission/src/main/java/my/itgungnir/permission/GPermissionDialog.kt +++ b/permission/src/main/java/my/itgungnir/permission/GPermissionDialog.kt @@ -10,7 +10,6 @@ import android.view.ViewGroup import android.view.Window import androidx.fragment.app.DialogFragment import kotlinx.android.synthetic.main.dialog_g_permission.* -import org.jetbrains.anko.backgroundDrawable class GPermissionDialog private constructor() : DialogFragment() { @@ -36,7 +35,7 @@ class GPermissionDialog private constructor() : DialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - view.backgroundDrawable = GradientDrawable().apply { + view.background = GradientDrawable().apply { setColor(Color.WHITE) cornerRadius = 10F } diff --git a/permission/src/main/java/my/itgungnir/permission/GPermissionFragment.kt b/permission/src/main/java/my/itgungnir/permission/GPermissionFragment.kt new file mode 100644 index 0000000..726c1ee --- /dev/null +++ b/permission/src/main/java/my/itgungnir/permission/GPermissionFragment.kt @@ -0,0 +1,69 @@ +package my.itgungnir.permission + +import android.annotation.TargetApi +import android.content.pm.PackageManager +import android.os.Build +import android.os.Bundle +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import io.reactivex.subjects.PublishSubject + +class GPermissionFragment private constructor() : Fragment() { + + private val subjects = mutableMapOf>() + + companion object { + const val PERMISSION_REQUEST_CODE = 0x1F + + fun getInstance(manager: FragmentManager): GPermissionFragment = + (manager.findFragmentByTag(GPermissionFragment::class.java.name) ?: GPermissionFragment().apply { + manager.beginTransaction().add(this, GPermissionFragment::class.java.name).commitNow() + }) as GPermissionFragment + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + retainInstance = true + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (PERMISSION_REQUEST_CODE != requestCode) { + return + } + for (i in 0 until permissions.size) { + val subject = subjects[permissions[i]] ?: return + subjects.remove(permissions[i]) + val granted = grantResults[i] == PackageManager.PERMISSION_GRANTED + subject.onNext( + Permission( + name = permissions[i], + granted = granted, + shouldShowRequestPermissionRationale = shouldShowRequestPermissionRationale(permissions[i]) + ) + ) + subject.onComplete() + } + } + + fun requestPermissions(permissions: Array) = + requestPermissions(permissions, PERMISSION_REQUEST_CODE) + + fun getPermissionSubject(permission: String) = + subjects[permission] + + fun setPermissionSubject(permission: String, subject: PublishSubject) { + subjects[permission] = subject + } + + fun isPermissionSubjectExist(permission: String) = + subjects.containsKey(permission) + + @TargetApi(Build.VERSION_CODES.M) + fun isPermissionGranted(permission: String): Boolean = + activity?.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED + + @TargetApi(Build.VERSION_CODES.M) + fun isPermissionRevoked(permission: String): Boolean = + activity?.packageManager?.isPermissionRevokedByPolicy(permission, activity!!.packageName) ?: false +} \ No newline at end of file diff --git a/permission/src/main/java/my/itgungnir/permission/GPermissionProxy.kt b/permission/src/main/java/my/itgungnir/permission/GPermissionProxy.kt new file mode 100644 index 0000000..fffc3ac --- /dev/null +++ b/permission/src/main/java/my/itgungnir/permission/GPermissionProxy.kt @@ -0,0 +1,103 @@ +package my.itgungnir.permission + +import android.os.Build +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.ViewModelStoreOwner +import io.reactivex.Observable +import io.reactivex.ObservableTransformer +import io.reactivex.subjects.PublishSubject +import java.util.* + +class GPermissionProxy { + + private lateinit var fragment: GPermissionFragment + + private val TRIGGER = Unit + + companion object { + fun with(component: ViewModelStoreOwner) = GPermissionProxy().apply { + fragment = when (component) { + is FragmentActivity -> GPermissionFragment.getInstance(component.supportFragmentManager) + is Fragment -> GPermissionFragment.getInstance(component.childFragmentManager) + else -> throw IllegalArgumentException("GPermission requested from wrong component.") + } + } + } + + fun requestEachCombined(vararg permissions: String): Observable = + Observable.just(TRIGGER).compose(ensureEachCombined(*permissions)) + + private fun ensureEachCombined(vararg permissions: String): ObservableTransformer { + return ObservableTransformer { o -> + request(o, *permissions) + .buffer(permissions.size) + .flatMap { permissions -> + if (permissions.isEmpty()) { + Observable.empty() + } else { + val combineName = permissions.map { it.name }.reduce { acc, s -> "$acc, $s" } + val combineGranted = permissions.all { it.granted } + val combineRationale = permissions.any { it.shouldShowRequestPermissionRationale } + Observable.just(Permission(combineName, combineGranted, combineRationale)) + } + } + } + } + + private fun request(trigger: Observable<*>, vararg permissions: String): Observable { + if (permissions.isEmpty()) { + throw IllegalArgumentException("RxPermissions.request/requestEach requires at least one input permission") + } + return oneOf(trigger, pending(*permissions)) + .flatMap { requestImplementation(*permissions) } + } + + private fun oneOf(trigger: Observable<*>?, pending: Observable<*>): Observable<*> { + return if (trigger == null) { + Observable.just(TRIGGER) + } else Observable.merge(trigger, pending) + } + + private fun pending(vararg permissions: String): Observable<*> { + for (p in permissions) { + if (!fragment.isPermissionSubjectExist(p)) { + return Observable.empty() + } + } + return Observable.just(TRIGGER) + } + + private fun requestImplementation(vararg permissions: String): Observable { + val list = ArrayList>(permissions.size) + val unrequestedPermissions = ArrayList() + for (permission in permissions) { + if (isGranted(permission)) { + list.add(Observable.just(Permission(permission, true, false))) + continue + } + if (isRevoked(permission)) { + list.add(Observable.just(Permission(permission, false, false))) + continue + } + var subject = fragment.getPermissionSubject(permission) + if (subject == null) { + unrequestedPermissions.add(permission) + subject = PublishSubject.create() + fragment.setPermissionSubject(permission, subject) + } + list.add(subject) + } + if (unrequestedPermissions.isNotEmpty()) { + val unrequestedPermissionsArray = unrequestedPermissions.toTypedArray() + fragment.requestPermissions(unrequestedPermissionsArray) + } + return Observable.concat(Observable.fromIterable(list)) + } + + private fun isGranted(permission: String) = + Build.VERSION.SDK_INT < Build.VERSION_CODES.M || fragment.isPermissionGranted(permission) + + private fun isRevoked(permission: String) = + Build.VERSION.SDK_INT < Build.VERSION_CODES.M || fragment.isPermissionRevoked(permission) +} \ No newline at end of file diff --git a/permission/src/main/java/my/itgungnir/permission/Permission.kt b/permission/src/main/java/my/itgungnir/permission/Permission.kt new file mode 100644 index 0000000..0ffb036 --- /dev/null +++ b/permission/src/main/java/my/itgungnir/permission/Permission.kt @@ -0,0 +1,7 @@ +package my.itgungnir.permission + +data class Permission( + val name: String, + val granted: Boolean, + val shouldShowRequestPermissionRationale: Boolean +) \ No newline at end of file diff --git a/permission/src/main/res/layout/dialog_g_permission.xml b/permission/src/main/res/layout/dialog_g_permission.xml index b4cf773..35420f3 100644 --- a/permission/src/main/res/layout/dialog_g_permission.xml +++ b/permission/src/main/res/layout/dialog_g_permission.xml @@ -1,67 +1,58 @@ - + android:textSize="14sp" /> + android:background="#CCCCCC" /> - + - + - - \ No newline at end of file + + + + + + \ No newline at end of file