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> { companion object { private val log = LogCategory("PermissionRequester") } private var launcher: ActivityResultLauncher>? = 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) { 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)