Skip to content
This repository has been archived by the owner on Sep 3, 2023. It is now read-only.

Commit

Permalink
Make detecting permanently denied not reliant on the use of a rationa…
Browse files Browse the repository at this point in the history
…le handler
  • Loading branch information
afollestad committed Jan 21, 2020
1 parent 1f60174 commit 59fde1d
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 96 deletions.
23 changes: 14 additions & 9 deletions core/src/main/java/com/afollestad/assent/Activities.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import android.content.Intent
import android.net.Uri
import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
import com.afollestad.assent.internal.Assent.Companion.ensureFragment
import com.afollestad.assent.internal.log
import com.afollestad.assent.rationale.RationaleHandler
import com.afollestad.assent.rationale.RealShouldShowRationale
import com.afollestad.assent.rationale.ShouldShowRationale

typealias Callback = (result: AssentResult) -> Unit

Expand All @@ -36,13 +37,18 @@ fun Activity.askForPermissions(
requestCode: Int = 20,
rationaleHandler: RationaleHandler? = null,
callback: Callback
) = startPermissionRequest(
attacher = { activity -> ensureFragment(activity) },
permissions = permissions,
requestCode = requestCode,
rationaleHandler = rationaleHandler,
callback = callback
)
) {
val prefs: Prefs = RealPrefs(this)
val shouldShowRationale: ShouldShowRationale = RealShouldShowRationale(this, prefs)
startPermissionRequest(
ensure = { activity -> ensureFragment(activity) },
permissions = permissions,
requestCode = requestCode,
shouldShowRationale = shouldShowRationale,
rationaleHandler = rationaleHandler,
callback = callback
)
}

/**
* Like [askForPermissions], but only executes the [execute] callback if all given
Expand All @@ -54,7 +60,6 @@ fun Activity.runWithPermissions(
rationaleHandler: RationaleHandler? = null,
execute: Callback
) {
log("runWithPermissions($permissions)")
askForPermissions(
*permissions,
requestCode = requestCode,
Expand Down
8 changes: 5 additions & 3 deletions core/src/main/java/com/afollestad/assent/AssentResult.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.annotation.CheckResult
import com.afollestad.assent.GrantResult.DENIED
import com.afollestad.assent.GrantResult.GRANTED
import com.afollestad.assent.GrantResult.PERMANENTLY_DENIED
import com.afollestad.assent.rationale.ShouldShowRationale

/**
* Wraps a result for a permission request, which provides utility
Expand All @@ -44,8 +45,9 @@ class AssentResult(

internal constructor(
permissions: Set<Permission>,
grantResults: IntArray
) : this(permissions, grantResults.mapGrantResults())
grantResults: IntArray,
shouldShowRationale: ShouldShowRationale
) : this(permissions, grantResults.mapGrantResults(permissions, shouldShowRationale))

/** @return the [GrantResult] for a given [permission]. */
@CheckResult operator fun get(permission: Permission): GrantResult =
Expand Down Expand Up @@ -98,7 +100,7 @@ class AssentResult(
}

override fun toString(): String {
return resultsMap.entries.joinToString(separator = "\n") { "${it.key} -> ${it.value}" }
return resultsMap.entries.joinToString(separator = ", ") { "${it.key}=>${it.value}" }
}
}

Expand Down
17 changes: 12 additions & 5 deletions core/src/main/java/com/afollestad/assent/Contexts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.afollestad.assent.internal.PermissionFragment
import com.afollestad.assent.internal.equalsPermissions
import com.afollestad.assent.internal.log
import com.afollestad.assent.rationale.RationaleHandler
import com.afollestad.assent.rationale.ShouldShowRationale

/** @return `true` if ALL given [permissions] have been granted. */
@CheckResult fun Context.isAllGranted(vararg permissions: Permission): Boolean {
Expand All @@ -36,25 +37,31 @@ import com.afollestad.assent.rationale.RationaleHandler
}

internal fun <T : Any> T.startPermissionRequest(
attacher: (T) -> PermissionFragment,
ensure: (T) -> PermissionFragment,
permissions: Array<out Permission>,
requestCode: Int = 20,
shouldShowRationale: ShouldShowRationale,
rationaleHandler: RationaleHandler? = null,
callback: Callback
) {
log("askForPermissions(${permissions.joinToString()})")
log("startPermissionRequest(%s)", permissions.joinToString())
// This invalidates the `shouldShowRationale` cache to help detect permanently denied early.
permissions.forEach { shouldShowRationale.check(it) }

if (rationaleHandler != null) {
rationaleHandler.requestPermissions(permissions, requestCode, callback)
return
}

val currentRequest = get().currentPendingRequest
val currentRequest: PendingRequest? = get().currentPendingRequest
if (currentRequest != null &&
currentRequest.permissions.equalsPermissions(*permissions)
) {
// Request matches permissions, append a callback
log("Callback appended to existing matching request")
log(
"Callback appended to existing matching request for %s",
permissions.joinToString()
)
currentRequest.callbacks.add(callback)
return
}
Expand All @@ -70,7 +77,7 @@ internal fun <T : Any> T.startPermissionRequest(
// There is no active request so we can execute immediately
get().currentPendingRequest = newPendingRequest
log("New request, performing now")
attacher(this).perform(newPendingRequest)
ensure(this).perform(newPendingRequest)
} else {
// There is an active request, append this new one to the queue
if (currentRequest.requestCode == requestCode) {
Expand Down
28 changes: 17 additions & 11 deletions core/src/main/java/com/afollestad/assent/Fragments.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
import androidx.annotation.CheckResult
import androidx.fragment.app.Fragment
import com.afollestad.assent.internal.Assent.Companion.ensureFragment
import com.afollestad.assent.internal.log
import com.afollestad.assent.rationale.RationaleHandler
import com.afollestad.assent.rationale.RealShouldShowRationale
import com.afollestad.assent.rationale.ShouldShowRationale

/** @return `true` if ALL given [permissions] have been granted. */
@CheckResult fun Fragment.isAllGranted(vararg permissions: Permission) =
activity?.isAllGranted(*permissions) ?: error("Fragment's Activity is null.")
activity?.isAllGranted(*permissions) ?: error("Fragment Activity is null: $this")

/**
* Performs a permission request, asking for all given [permissions], and
Expand All @@ -39,13 +40,19 @@ fun Fragment.askForPermissions(
requestCode: Int = 60,
rationaleHandler: RationaleHandler? = null,
callback: Callback
) = startPermissionRequest(
attacher = { fragment -> ensureFragment(fragment) },
permissions = permissions,
requestCode = requestCode,
rationaleHandler = rationaleHandler?.withOwner(this),
callback = callback
)
) {
val activity = activity ?: error("Fragment not attached: $this")
val prefs: Prefs = RealPrefs(activity)
val shouldShowRationale: ShouldShowRationale = RealShouldShowRationale(activity, prefs)
startPermissionRequest(
ensure = { fragment -> ensureFragment(fragment) },
permissions = permissions,
requestCode = requestCode,
shouldShowRationale = shouldShowRationale,
rationaleHandler = rationaleHandler?.withOwner(this),
callback = callback
)
}

/**
* Like [askForPermissions], but only executes the [execute] callback if all given
Expand All @@ -57,7 +64,6 @@ fun Fragment.runWithPermissions(
rationaleHandler: RationaleHandler? = null,
execute: Callback
) {
log("runWithPermissions($permissions)")
askForPermissions(
*permissions,
requestCode = requestCode,
Expand All @@ -74,7 +80,7 @@ fun Fragment.runWithPermissions(
* denied.
*/
fun Fragment.showSystemAppDetailsPage() {
val context = requireNotNull(context) { "Fragment's context is null, is it attached?" }
val context = requireNotNull(context) { "Fragment context is null, is it attached? $this" }
startActivity(Intent(ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.parse("package:${context.packageName}")
})
Expand Down
20 changes: 17 additions & 3 deletions core/src/main/java/com/afollestad/assent/GrantResult.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package com.afollestad.assent
import android.content.pm.PackageManager
import com.afollestad.assent.GrantResult.DENIED
import com.afollestad.assent.GrantResult.GRANTED
import com.afollestad.assent.GrantResult.PERMANENTLY_DENIED
import com.afollestad.assent.rationale.ShouldShowRationale

/** @author Aidan Follestad (@afollestad) */
enum class GrantResult {
Expand All @@ -26,13 +28,25 @@ enum class GrantResult {
PERMANENTLY_DENIED
}

internal fun Int.asGrantResult(): GrantResult {
internal fun Int.asGrantResult(
forPermission: Permission,
shouldShowRationale: ShouldShowRationale
): GrantResult {
if (shouldShowRationale.isPermanentlyDenied(forPermission)) {
return PERMANENTLY_DENIED
}
return when (this) {
PackageManager.PERMISSION_GRANTED -> GRANTED
else -> DENIED
}
}

internal fun IntArray.mapGrantResults(): List<GrantResult> {
return map { it.asGrantResult() }
internal fun IntArray.mapGrantResults(
permissions: Set<Permission>,
shouldShowRationale: ShouldShowRationale
): List<GrantResult> {
return mapIndexed { index, grantResult ->
val permission: Permission = permissions.elementAt(index)
grantResult.asGrantResult(permission, shouldShowRationale)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity

internal class Assent {

internal val requestQueue = Queue<PendingRequest>()
internal var currentPendingRequest: PendingRequest? = null
internal var permissionFragment: PermissionFragment? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,18 @@ package com.afollestad.assent.internal
import android.content.Context
import androidx.fragment.app.Fragment
import com.afollestad.assent.AssentResult
import com.afollestad.assent.RealPrefs
import com.afollestad.assent.internal.Assent.Companion.ensureFragment
import com.afollestad.assent.internal.Assent.Companion.forgetFragment
import com.afollestad.assent.internal.Assent.Companion.get
import com.afollestad.assent.rationale.RealShouldShowRationale

/** @author Aidan Follestad (afollestad) */
class PermissionFragment : Fragment() {

override fun onAttach(context: Context) {
super.onAttach(context)
log("onAttach($context)")
log("onAttach(%s)", context)
}

override fun onDetach() {
Expand All @@ -36,19 +38,19 @@ class PermissionFragment : Fragment() {
}

internal fun perform(request: PendingRequest) {
log("perform($request)")
this.requestPermissions(request.permissions.allValues(), request.requestCode)
log("perform(%s)", request)
requestPermissions(request.permissions.allValues(), request.requestCode)
}

internal fun detach() {
if (parentFragment != null) {
log("Detaching PermissionFragment from parent fragment $parentFragment")
log("Detaching PermissionFragment from parent Fragment %s", parentFragment)
parentFragment?.transact {
detach(this@PermissionFragment)
remove(this@PermissionFragment)
}
} else if (activity != null) {
log("Detaching PermissionFragment from Activity $activity")
log("Detaching PermissionFragment from Activity %s", activity)
activity?.transact {
detach(this@PermissionFragment)
remove(this@PermissionFragment)
Expand All @@ -73,39 +75,37 @@ internal fun Fragment.onPermissionsResponse(
permissions: Array<out String>,
grantResults: IntArray
) {
log(
"onPermissionsResponse(\n\tpermissions = %s,\n\tgrantResults = %s\n))",
permissions.joinToString(),
grantResults.joinToString()
val prefs = RealPrefs(context!!)
val shouldShowRationale = RealShouldShowRationale(activity!!, prefs)
val result = AssentResult(
permissions = permissions.toPermissions(),
grantResults = grantResults,
shouldShowRationale = shouldShowRationale
)
log("onPermissionsResponse(): %s", result)

val currentRequest = get().currentPendingRequest
val currentRequest: PendingRequest? = get().currentPendingRequest
if (currentRequest == null) {
warn("response() called but there's no current pending request.")
warn("onPermissionsResponse() called but there's no current pending request.")
return
}

if (currentRequest.permissions.equalsStrings(permissions)) {
// Execute the response
val result = AssentResult(
permissions = permissions.toPermissions(),
grantResults = grantResults
)
log("Executing response for ${permissions.joinToString()}")
currentRequest.callbacks.invokeAll(result)
get().currentPendingRequest = null
} else {
warn(
"onPermissionsResponse() called with a result that doesn't match the current pending request."
"onPermissionsResponse() called with a result " +
"that doesn't match the current pending request."
)
return
}

if (get().requestQueue.isNotEmpty()) {
// Execute the next request in the queue
val nextRequest = get().requestQueue.pop()
get().currentPendingRequest = nextRequest
log("Executing next request in the queue")
val nextRequest: PendingRequest = get().requestQueue.pop()
.also { get().currentPendingRequest = it }
log("Executing next request in the queue: %s", nextRequest)
ensureFragment(this@onPermissionsResponse).perform(nextRequest)
} else {
// No more requests to execute, we can destroy the Fragment
Expand Down
Loading

0 comments on commit 59fde1d

Please sign in to comment.