154 lines
5.7 KiB
Kotlin
154 lines
5.7 KiB
Kotlin
package jp.juggler.subwaytooter.util
|
|
|
|
import android.app.AlertDialog
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.provider.Settings
|
|
import androidx.activity.result.ActivityResultCallback
|
|
import androidx.activity.result.ActivityResultLauncher
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
|
import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale
|
|
import androidx.core.net.toUri
|
|
import androidx.fragment.app.Fragment
|
|
import androidx.fragment.app.FragmentActivity
|
|
import jp.juggler.subwaytooter.R
|
|
import jp.juggler.util.coroutine.launchMain
|
|
import jp.juggler.util.log.LogCategory
|
|
import jp.juggler.util.log.showToast
|
|
import jp.juggler.util.ui.dismissSafe
|
|
import kotlinx.coroutines.CancellationException
|
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
import kotlin.coroutines.resumeWithException
|
|
|
|
/**
|
|
* ActivityResultLauncherを使ってパーミッション要求とその結果の処理を行う
|
|
*/
|
|
class PermissionRequester(
|
|
/**
|
|
* 権限の詳細
|
|
*/
|
|
val spec: PermissionSpec,
|
|
/**
|
|
* 権限が与えられた際に処理を再開するラムダ
|
|
* - ラムダの引数にこのPermissionRequester自身が渡される
|
|
*/
|
|
val onGrant: (PermissionRequester) -> Unit,
|
|
) : ActivityResultCallback<Map<String, Boolean>> {
|
|
companion object {
|
|
private val log = LogCategory("PermissionRequester")
|
|
}
|
|
|
|
private var launcher: ActivityResultLauncher<Array<String>>? = null
|
|
|
|
private var getContext: (() -> Context?)? = null
|
|
|
|
private val activity
|
|
get() = getContext?.invoke() as? FragmentActivity
|
|
|
|
// ActivityのonCreate()から呼び出す
|
|
fun register(activity: FragmentActivity) {
|
|
getContext = { activity }
|
|
launcher = activity.registerForActivityResult(
|
|
ActivityResultContracts.RequestMultiplePermissions(),
|
|
this,
|
|
)
|
|
}
|
|
|
|
// FragmentのonCreate()から呼び出す
|
|
fun register(fragment: Fragment) {
|
|
getContext = { fragment.activity ?: fragment.context }
|
|
launcher = fragment.registerForActivityResult(
|
|
ActivityResultContracts.RequestMultiplePermissions(),
|
|
this,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* 実行時権限が全て揃っているならtrueを返す
|
|
* そうでなければ権限の要求を行い、falseを返す
|
|
*/
|
|
fun checkOrLaunch(): Boolean {
|
|
val activity = activity ?: error("missing activity.")
|
|
|
|
val listNotGranted = spec.listNotGranded(activity)
|
|
|
|
if (listNotGranted.isEmpty()) return true
|
|
|
|
launchMain {
|
|
try {
|
|
val shouldShowRational = listNotGranted.any {
|
|
shouldShowRequestPermissionRationale(activity, it)
|
|
}
|
|
|
|
if (shouldShowRational && spec.rationalId != 0) {
|
|
suspendCancellableCoroutine { cont ->
|
|
AlertDialog.Builder(activity)
|
|
.setMessage(spec.rationalId)
|
|
.setNegativeButton(R.string.cancel, null)
|
|
.setPositiveButton(R.string.ok) { _, _ ->
|
|
if (cont.isActive) cont.resumeWith(Result.success(Unit))
|
|
}
|
|
.setOnDismissListener {
|
|
if (cont.isActive) cont.resumeWithException(CancellationException())
|
|
}
|
|
.create()
|
|
.also { dialog -> cont.invokeOnCancellation { dialog.dismissSafe() } }
|
|
.show()
|
|
}
|
|
}
|
|
when (val launcher = launcher) {
|
|
null -> error("launcher not registered.")
|
|
else -> launcher.launch(listNotGranted.toTypedArray())
|
|
}
|
|
} catch (ex: Throwable) {
|
|
if (ex !is CancellationException) {
|
|
activity.showToast(ex, "can't request permissions.")
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* 権限要求の結果を処理する
|
|
* @param result 「パーミッション名」と「それが許可されているなら真」のマップ
|
|
*/
|
|
override fun onActivityResult(result: Map<String, Boolean>) {
|
|
try {
|
|
val listNotGranted = result.entries.filter { !it.value }.map { it.key }
|
|
if (listNotGranted.isEmpty()) {
|
|
// すべて許可されている
|
|
onGrant(this)
|
|
return
|
|
}
|
|
// 許可されなかった。
|
|
val activity = activity ?: error("missing activity.")
|
|
AlertDialog.Builder(activity)
|
|
.setMessage(spec.deniedId)
|
|
.setNegativeButton(R.string.cancel, null)
|
|
.setPositiveButton(R.string.setting) { _, _ ->
|
|
openAppSetting(activity)
|
|
}
|
|
.show()
|
|
} catch (ex: Throwable) {
|
|
log.e(ex, "onActivityResult: can't handle result.")
|
|
}
|
|
}
|
|
|
|
fun openAppSetting(activity: FragmentActivity) {
|
|
try {
|
|
Intent(
|
|
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
|
"package:${activity.packageName}".toUri()
|
|
).apply {
|
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
}.let { activity.startActivity(it) }
|
|
} catch (ex: Throwable) {
|
|
activity.showToast(ex, "openAppSetting failed.")
|
|
}
|
|
}
|
|
}
|
|
|
|
fun PermissionSpec.requester(onGrant: (PermissionRequester) -> Unit) =
|
|
PermissionRequester(this, onGrant)
|