Skip to content

Commit 2c70555

Browse files
authored
Merge pull request #9 from merail/develop
1.1.0
2 parents 5bb2568 + b03886d commit 2c70555

File tree

14 files changed

+276
-52
lines changed

14 files changed

+276
-52
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,5 @@ afterEvaluate {
5656
dependencies {
5757
implementation("androidx.appcompat:appcompat:1.6.1")
5858
implementation("com.google.android.material:material:1.11.0")
59-
implementation("androidx.core:core-ktx:1.12.0")
59+
implementation("androidx.core:core-ktx:1.13.0")
6060
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package merail.tools.permissions.core.common
2+
3+
internal class SdkIncompatibilityException : Exception()
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
11
package merail.tools.permissions.core.common
22

3-
internal const val TAG = "MERAIL_TOOLS"
3+
import android.app.Activity
4+
import android.content.pm.PackageManager
5+
6+
internal const val TAG = "MERAIL_TOOLS"
7+
8+
internal fun Activity.isPermissionDeclaredInManifest(
9+
permission: String,
10+
) = packageManager.getPackageInfo(
11+
packageName,
12+
PackageManager.GET_PERMISSIONS,
13+
).requestedPermissions
14+
?.any {
15+
it == permission
16+
} ?: false
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package merail.tools.permissions.core.role
2+
3+
import android.util.Log
4+
import merail.tools.permissions.core.common.TAG
5+
import merail.tools.permissions.role.RoleState
6+
7+
internal class RoleResultObserver {
8+
fun invoke(
9+
role: String,
10+
isGranted: Boolean,
11+
) = role to when {
12+
isGranted -> {
13+
Log.d(TAG, "Role \"$role\" is granted")
14+
RoleState.GRANTED
15+
}
16+
17+
else -> {
18+
Log.d(TAG, "Role \"$role\" is denied")
19+
RoleState.DENIED
20+
}
21+
}
22+
}

app/src/main/java/merail/tools/permissions/core/runtime/RuntimePermissionResultObserver.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import android.util.Log
77
import androidx.activity.ComponentActivity
88
import androidx.core.app.ActivityCompat
99
import merail.tools.permissions.core.common.TAG
10+
import merail.tools.permissions.core.common.isPermissionDeclaredInManifest
1011
import merail.tools.permissions.inform.PermissionsInformer
1112
import merail.tools.permissions.runtime.RuntimePermissionState
1213

@@ -21,6 +22,10 @@ internal class RuntimePermissionResultObserver(
2122
Log.e(TAG, "Permission \"${entry.key}\" is unknown. Can't handle it")
2223
RuntimePermissionState.IGNORED
2324
}
25+
entry.isNotDeclaredInManifest() -> {
26+
Log.e(TAG, "Permission \"${entry.key}\" isn't declared in Manifest!")
27+
RuntimePermissionState.DENIED
28+
}
2429
entry.isInstallTime() -> {
2530
Log.i(TAG, "Permission \"${entry.key}\" is install-time and normal. Declaring this permission in the manifest is sufficient to obtain it")
2631
RuntimePermissionState.GRANTED
@@ -76,6 +81,10 @@ internal class RuntimePermissionResultObserver(
7681
}
7782
}
7883

84+
private fun Map.Entry<String, Boolean>.isNotDeclaredInManifest() = activity.
85+
isPermissionDeclaredInManifest(
86+
permission = key,
87+
).not()
7988

8089
private fun Map.Entry<String, Boolean>.isUnknown() = permissionsInformer.isUnknown(key)
8190

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package merail.tools.permissions.core.special
2+
3+
import android.util.Log
4+
import androidx.activity.ComponentActivity
5+
import merail.tools.permissions.core.common.TAG
6+
import merail.tools.permissions.core.common.isPermissionDeclaredInManifest
7+
import merail.tools.permissions.inform.PermissionsInformer
8+
import merail.tools.permissions.special.SpecialPermissionState
9+
10+
internal class SpecialPermissionResultObserver(
11+
private val activity: ComponentActivity,
12+
) {
13+
private val permissionsInformer = PermissionsInformer(activity)
14+
15+
fun invoke(
16+
type: SpecialPermissionType,
17+
isGranted: Boolean,
18+
) = with(type) {
19+
permission to when {
20+
permissionsInformer.isUnknown(permission) -> {
21+
Log.e(TAG, "Permission \"$permission\" is unknown. Can't handle it")
22+
SpecialPermissionState.DENIED
23+
}
24+
activity.isPermissionDeclaredInManifest(permission).not() -> {
25+
Log.e(TAG, "Permission \"$permission\" isn't declared in Manifest!")
26+
SpecialPermissionState.DENIED
27+
}
28+
permissionsInformer.isInstallTime(permission) -> {
29+
Log.i(TAG, "Permission \"$permission\" is install-time and normal. Declaring this permission in the manifest is sufficient to obtain it")
30+
SpecialPermissionState.DENIED
31+
}
32+
permissionsInformer.isRuntime(permission) -> {
33+
Log.w(TAG, "Permission \"$permission\" is runtime. Try using RuntimePermissionRequester to get it")
34+
SpecialPermissionState.DENIED
35+
}
36+
permissionsInformer.isSystem(permission) -> {
37+
Log.w(TAG, "Permission \"$permission\" is system. This permission is only granted to system apps")
38+
SpecialPermissionState.DENIED
39+
}
40+
type is SpecialPermissionType.Unknown -> {
41+
Log.w(TAG, "SpecialPermissionRequester currently doesn't have implementation for permission \"$permission\"")
42+
SpecialPermissionState.DENIED
43+
}
44+
isGranted -> {
45+
Log.d(TAG, "Permission \"$permission\" is granted")
46+
SpecialPermissionState.GRANTED
47+
}
48+
else -> {
49+
Log.d(TAG, "Permission \"$permission\" is denied")
50+
SpecialPermissionState.DENIED
51+
}
52+
}
53+
}
54+
}
Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,144 @@
11
package merail.tools.permissions.core.special
22

3+
import android.Manifest
4+
import android.annotation.SuppressLint
35
import android.app.AlarmManager
46
import android.os.Build
57
import android.os.Environment
68
import android.provider.MediaStore
79
import android.provider.Settings
810
import androidx.activity.ComponentActivity
911
import androidx.core.content.ContextCompat
12+
import merail.tools.permissions.core.common.SdkIncompatibilityException
1013
import merail.tools.permissions.core.common.SettingsOpener
1114

1215

13-
internal sealed class SpecialPermissionType {
16+
internal sealed class SpecialPermissionType(
17+
open val permission: String,
18+
) {
1419

1520
abstract fun isGranted(): Boolean
1621

1722
abstract fun requestPermission()
1823

24+
@SuppressLint("InlinedApi")
1925
class ManageExternalStorage(
2026
val activity: ComponentActivity,
21-
) : SpecialPermissionType() {
27+
) : SpecialPermissionType(Manifest.permission.MANAGE_EXTERNAL_STORAGE) {
2228
override fun isGranted() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
2329
Environment.isExternalStorageManager()
2430
} else {
25-
true
31+
false
2632
}
2733

2834
override fun requestPermission() {
2935
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
3036
SettingsOpener.openSettings(activity, Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
37+
} else {
38+
throw SdkIncompatibilityException()
3139
}
3240
}
3341
}
3442

43+
@SuppressLint("InlinedApi")
3544
class ManageMedia(
3645
val activity: ComponentActivity,
37-
) : SpecialPermissionType() {
46+
) : SpecialPermissionType(Manifest.permission.MANAGE_MEDIA) {
3847
override fun isGranted() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
3948
MediaStore.canManageMedia(activity)
4049
} else {
41-
true
50+
false
4251
}
4352

4453
override fun requestPermission() {
4554
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
4655
SettingsOpener.openSettings(activity, Settings.ACTION_REQUEST_MANAGE_MEDIA)
56+
} else {
57+
throw SdkIncompatibilityException()
4758
}
4859
}
4960
}
5061

62+
@SuppressLint("InlinedApi")
5163
class RequestInstallPackages(
5264
val activity: ComponentActivity,
53-
) : SpecialPermissionType() {
65+
) : SpecialPermissionType(Manifest.permission.REQUEST_INSTALL_PACKAGES) {
5466
override fun isGranted() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
5567
activity.packageManager.canRequestPackageInstalls()
5668
} else {
57-
true
69+
false
5870
}
5971

6072
override fun requestPermission() {
6173
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
6274
SettingsOpener.openSettings(activity, Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
75+
} else {
76+
throw SdkIncompatibilityException()
6377
}
6478
}
6579
}
6680

81+
@SuppressLint("InlinedApi")
6782
class ScheduleExactAlarm(
6883
val activity: ComponentActivity,
69-
) : SpecialPermissionType() {
84+
) : SpecialPermissionType(Manifest.permission.SCHEDULE_EXACT_ALARM) {
7085
override fun isGranted() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
7186
val alarmManager = ContextCompat.getSystemService(activity, AlarmManager::class.java)
7287
alarmManager?.canScheduleExactAlarms() == true
7388
} else {
74-
true
89+
false
7590
}
7691

7792
override fun requestPermission() {
7893
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
7994
SettingsOpener.openSettings(activity, Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
95+
} else {
96+
throw SdkIncompatibilityException()
8097
}
8198
}
8299
}
83100

84101
class SystemAlertWindow(
85102
val activity: ComponentActivity,
86-
) : SpecialPermissionType() {
103+
) : SpecialPermissionType(Manifest.permission.SYSTEM_ALERT_WINDOW) {
87104
override fun isGranted() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
88105
Settings.canDrawOverlays(activity)
89106
} else {
90-
true
107+
false
91108
}
92109

93110
override fun requestPermission() {
94-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
111+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
95112
SettingsOpener.openSettings(activity, Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
113+
} else {
114+
throw SdkIncompatibilityException()
96115
}
97116
}
98117
}
99118

100119
class WriteSettings(
101120
val activity: ComponentActivity,
102-
) : SpecialPermissionType() {
121+
) : SpecialPermissionType(Manifest.permission.WRITE_SETTINGS) {
103122
override fun isGranted() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
104123
Settings.System.canWrite(activity)
105124
} else {
106-
true
125+
false
107126
}
108127

109128
override fun requestPermission() {
110129
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
111130
SettingsOpener.openSettings(activity, Settings.ACTION_MANAGE_WRITE_SETTINGS)
131+
} else {
132+
throw SdkIncompatibilityException()
112133
}
113134
}
114135
}
115136

116-
object Unknown : SpecialPermissionType() {
137+
class Unknown(
138+
override val permission: String,
139+
) : SpecialPermissionType(permission) {
117140
override fun isGranted() = false
118141

119-
override fun requestPermission() = Unit
142+
override fun requestPermission() = throw SdkIncompatibilityException()
120143
}
121144
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package merail.tools.permissions.role
2+
3+
import android.app.Activity
4+
import android.app.role.RoleManager
5+
import android.content.Context
6+
import android.content.Intent
7+
import android.os.Build
8+
import androidx.activity.ComponentActivity
9+
import androidx.activity.result.ActivityResultLauncher
10+
import androidx.activity.result.contract.ActivityResultContracts
11+
import androidx.annotation.RequiresApi
12+
import merail.tools.permissions.WrongTimeInitializationException
13+
import merail.tools.permissions.core.role.RoleResultObserver
14+
15+
@RequiresApi(Build.VERSION_CODES.Q)
16+
class RoleRequester(
17+
activity: ComponentActivity,
18+
var requestedRole: String,
19+
) {
20+
private val roleManager = activity.getSystemService(Context.ROLE_SERVICE) as RoleManager
21+
22+
private var onRoleRequestResult: ((Pair<String, RoleState>) -> Unit)? = null
23+
24+
private val requestRoleLauncher: ActivityResultLauncher<Intent>
25+
26+
init {
27+
try {
28+
val roleResultObserver = RoleResultObserver()
29+
requestRoleLauncher = activity.registerForActivityResult(
30+
ActivityResultContracts.StartActivityForResult(),
31+
) {
32+
val roleRequestResult = roleResultObserver.invoke(
33+
role = requestedRole,
34+
isGranted = it.resultCode == Activity.RESULT_OK,
35+
)
36+
onRoleRequestResult?.invoke(roleRequestResult)
37+
}
38+
} catch (exception: Exception) {
39+
throw WrongTimeInitializationException()
40+
}
41+
}
42+
43+
fun isRoleGranted() = roleManager.isRoleHeld(requestedRole)
44+
45+
fun requestRole(
46+
onRoleRequestResult: ((Pair<String, RoleState>) -> Unit)? = null,
47+
) {
48+
this.onRoleRequestResult = onRoleRequestResult
49+
val intent = roleManager.createRequestRoleIntent(requestedRole)
50+
requestRoleLauncher.launch(intent)
51+
}
52+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package merail.tools.permissions.role
2+
3+
enum class RoleState {
4+
GRANTED,
5+
DENIED,
6+
}

0 commit comments

Comments
 (0)