Merge "Enable teamfood on the bouncer messages feature" into main
diff --git a/core/java/android/database/sqlite/package.html b/core/java/android/database/sqlite/package.html
index 2c36646..f2df536 100644
--- a/core/java/android/database/sqlite/package.html
+++ b/core/java/android/database/sqlite/package.html
@@ -15,7 +15,7 @@
 <a href="{@docRoot}studio/command-line/sqlite3.html">sqlite3</a> command-line
 database tool. On your development machine, run the tool from the
 <code>platform-tools/</code> folder of your SDK. On the emulator, run the tool
-with adb shell, for example, <code>adb -e shell sqlite3</code>.
+with adb shell, for example, <code>adb shell sqlite3</code>.
 
 <p>The version of SQLite depends on the version of Android. See the following table:
 <table style="width:auto;">
@@ -42,15 +42,19 @@
 
 <ul>
   <li>If available, use the sqlite3 tool, for example:
-    <code>adb -e shell sqlite3 --version</code>.</li>
+    <code>adb shell sqlite3 --version</code>.</li>
   <li>Create and query an in-memory database as shown in the following code sample:
     <pre>
     String query = "select sqlite_version() AS sqlite_version";
     SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(":memory:", null);
     Cursor cursor = db.rawQuery(query, null);
     String sqliteVersion = "";
-    if (cursor.moveToNext()) {
-        sqliteVersion = cursor.getString(0);
+    try {
+        if (cursor.moveToNext()) {
+            sqliteVersion = cursor.getString(0);
+        }
+    } finally {
+        cursor.close();
     }</pre>
   </li>
 </ul>
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 46a9d42..d0f2ce8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -48,13 +48,13 @@
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
-import com.android.systemui.res.R
 import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -104,7 +104,8 @@
     modifier: Modifier = Modifier,
 ) {
     val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
-    val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState()
+    val authMethodViewModel: AuthMethodBouncerViewModel? by
+        viewModel.authMethodViewModel.collectAsState()
     val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
     var dialog: Dialog? by remember { mutableStateOf(null) }
     val backgroundColor = MaterialTheme.colorScheme.surface
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 57a4224..4cfc6aa 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -185,9 +185,6 @@
     /** Whether the pattern should be visible for the currently-selected user. */
     val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible
 
-    /** The minimal length of a pattern. */
-    val minPatternLength: Int = repository.minPatternLength
-
     private var throttlingCountdownJob: Job? = null
 
     init {
@@ -243,39 +240,46 @@
      * Attempts to authenticate the user and unlock the device.
      *
      * If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method
-     * supports auto-confirming, and the input's length is at least the code's length. Otherwise,
-     * `null` is returned.
+     * supports auto-confirming, and the input's length is at least the required length. Otherwise,
+     * `AuthenticationResult.SKIPPED` is returned.
      *
      * @param input The input from the user to try to authenticate with. This can be a list of
      *   different things, based on the current authentication method.
      * @param tryAutoConfirm `true` if called while the user inputs the code, without an explicit
      *   request to validate.
-     * @return `true` if the authentication succeeded and the device is now unlocked; `false` when
-     *   authentication failed, `null` if the check was not performed.
+     * @return The result of this authentication attempt.
      */
-    suspend fun authenticate(input: List<Any>, tryAutoConfirm: Boolean = false): Boolean? {
+    suspend fun authenticate(
+        input: List<Any>,
+        tryAutoConfirm: Boolean = false
+    ): AuthenticationResult {
         if (input.isEmpty()) {
             throw IllegalArgumentException("Input was empty!")
         }
 
+        val authMethod = getAuthenticationMethod()
         val skipCheck =
             when {
                 // We're being throttled, the UI layer should not have called this; skip the
                 // attempt.
                 isThrottled.value -> true
+                // The pattern is too short; skip the attempt.
+                authMethod == DomainLayerAuthenticationMethodModel.Pattern &&
+                    input.size < repository.minPatternLength -> true
                 // Auto-confirm attempt when the feature is not enabled; skip the attempt.
                 tryAutoConfirm && !isAutoConfirmEnabled.value -> true
                 // Auto-confirm should skip the attempt if the pin entered is too short.
-                tryAutoConfirm && input.size < repository.getPinLength() -> true
+                tryAutoConfirm &&
+                    authMethod == DomainLayerAuthenticationMethodModel.Pin &&
+                    input.size < repository.getPinLength() -> true
                 else -> false
             }
         if (skipCheck) {
-            return null
+            return AuthenticationResult.SKIPPED
         }
 
         // Attempt to authenticate:
-        val authMethod = getAuthenticationMethod()
-        val credential = authMethod.createCredential(input) ?: return null
+        val credential = authMethod.createCredential(input) ?: return AuthenticationResult.SKIPPED
         val authenticationResult = repository.checkCredential(credential)
         credential.zeroize()
 
@@ -299,7 +303,11 @@
             refreshThrottling()
         }
 
-        return authenticationResult.isSuccessful
+        return if (authenticationResult.isSuccessful) {
+            AuthenticationResult.SUCCEEDED
+        } else {
+            AuthenticationResult.FAILED
+        }
     }
 
     /** Starts refreshing the throttling state every second. */
@@ -383,3 +391,16 @@
         }
     }
 }
+
+/** Result of a user authentication attempt. */
+enum class AuthenticationResult {
+    /** Authentication succeeded and the device is now unlocked. */
+    SUCCEEDED,
+    /** Authentication failed and the device remains unlocked. */
+    FAILED,
+    /**
+     * Authentication was not performed, e.g. due to insufficient input, and the device remains
+     * unlocked.
+     */
+    SKIPPED,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 9b2f2ba..bae0ac7 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.bouncer.domain.interactor
 
 import android.content.Context
-import com.android.systemui.res.R
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.domain.interactor.AuthenticationResult
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.bouncer.data.repository.BouncerRepository
@@ -26,6 +26,7 @@
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
@@ -34,6 +35,7 @@
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -92,9 +94,6 @@
     /** Whether the pattern should be visible for the currently-selected user. */
     val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible
 
-    /** The minimal length of a pattern. */
-    val minPatternLength = authenticationInteractor.minPatternLength
-
     init {
         if (flags.isEnabled()) {
             // Clear the message if moved from throttling to no-longer throttling.
@@ -184,33 +183,44 @@
      * dismissed and hidden.
      *
      * If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method
-     * supports auto-confirming, and the input's length is at least the code's length. Otherwise,
-     * `null` is returned.
+     * supports auto-confirming, and the input's length is at least the required length. Otherwise,
+     * `AuthenticationResult.SKIPPED` is returned.
      *
      * @param input The input from the user to try to authenticate with. This can be a list of
      *   different things, based on the current authentication method.
      * @param tryAutoConfirm `true` if called while the user inputs the code, without an explicit
      *   request to validate.
-     * @return `true` if the authentication succeeded and the device is now unlocked; `false` when
-     *   authentication failed, `null` if the check was not performed.
+     * @return The result of this authentication attempt.
      */
     suspend fun authenticate(
         input: List<Any>,
         tryAutoConfirm: Boolean = false,
-    ): Boolean? {
-        val isAuthenticated =
-            authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null
-
-        if (isAuthenticated) {
-            sceneInteractor.changeScene(
-                scene = SceneModel(SceneKey.Gone),
-                loggingReason = "successful authentication",
-            )
-        } else {
-            showErrorMessage()
+    ): AuthenticationResult {
+        if (input.isEmpty()) {
+            return AuthenticationResult.SKIPPED
         }
-
-        return isAuthenticated
+        // Switching to the application scope here since this method is often called from
+        // view-models, whose lifecycle (and thus scope) is shorter than this interactor.
+        // This allows the task to continue running properly even when the calling scope has been
+        // cancelled.
+        return applicationScope
+            .async {
+                val authResult = authenticationInteractor.authenticate(input, tryAutoConfirm)
+                when (authResult) {
+                    // Authentication succeeded.
+                    AuthenticationResult.SUCCEEDED ->
+                        sceneInteractor.changeScene(
+                            scene = SceneModel(SceneKey.Gone),
+                            loggingReason = "successful authentication",
+                        )
+                    // Authentication failed.
+                    AuthenticationResult.FAILED -> showErrorMessage()
+                    // Authentication skipped.
+                    AuthenticationResult.SKIPPED -> if (!tryAutoConfirm) showErrorMessage()
+                }
+                authResult
+            }
+            .await()
     }
 
     /**
@@ -221,7 +231,7 @@
      * For example, if the user entered a pattern that's too short, the system can show the error
      * message without having the attempt trigger throttling.
      */
-    suspend fun showErrorMessage() {
+    private suspend fun showErrorMessage() {
         repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod()))
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index 4546bea..0b0a8f5 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -16,12 +16,21 @@
 
 package com.android.systemui.bouncer.ui.viewmodel
 
+import android.annotation.StringRes
+import android.util.Log
+import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
 
 sealed class AuthMethodBouncerViewModel(
+    protected val viewModelScope: CoroutineScope,
+    protected val interactor: BouncerInteractor,
+
     /**
      * Whether user input is enabled.
      *
@@ -29,7 +38,6 @@
      * being able to attempt to unlock the device.
      */
     val isInputEnabled: StateFlow<Boolean>,
-    private val interactor: BouncerInteractor,
 ) {
 
     private val _animateFailure = MutableStateFlow(false)
@@ -42,12 +50,26 @@
     /** Whether the input method editor (for example, the software keyboard) is visible. */
     private var isImeVisible: Boolean = false
 
+    /** The authentication method that corresponds to this view model. */
+    abstract val authenticationMethod: AuthenticationMethodModel
+
     /**
-     * Notifies that the failure animation has been shown. This should be called to consume a `true`
-     * value in [animateFailure].
+     * String resource ID of the failure message to be shown during throttling.
+     *
+     * The message must include 2 number parameters: the first one indicating how many unsuccessful
+     * attempts were made, and the second one indicating in how many seconds throttling will expire.
      */
-    fun onFailureAnimationShown() {
-        _animateFailure.value = false
+    @get:StringRes abstract val throttlingMessageId: Int
+
+    /** Notifies that the UI has been shown to the user. */
+    fun onShown() {
+        clearInput()
+        interactor.resetMessage()
+    }
+
+    /** Notifies that the user has placed down a pointer. */
+    fun onDown() {
+        interactor.onDown()
     }
 
     /**
@@ -65,8 +87,38 @@
         isImeVisible = isVisible
     }
 
-    /** Ask the UI to show the failure animation. */
-    protected fun showFailureAnimation() {
-        _animateFailure.value = true
+    /**
+     * Notifies that the failure animation has been shown. This should be called to consume a `true`
+     * value in [animateFailure].
+     */
+    fun onFailureAnimationShown() {
+        _animateFailure.value = false
+    }
+
+    /** Clears any previously-entered input. */
+    protected abstract fun clearInput()
+
+    /** Returns the input entered so far. */
+    protected abstract fun getInput(): List<Any>
+
+    /**
+     * Attempts to authenticate the user using the current input value.
+     *
+     * @see BouncerInteractor.authenticate
+     */
+    protected fun tryAuthenticate(useAutoConfirm: Boolean = false) {
+        viewModelScope.launch {
+            Log.d("Danny", "tryAuthenticate(useAutoConfirm=$useAutoConfirm)")
+            val authenticationResult = interactor.authenticate(getInput(), useAutoConfirm)
+            Log.d("Danny", "result = $authenticationResult")
+            if (authenticationResult == AuthenticationResult.SKIPPED && useAutoConfirm) {
+                return@launch
+            }
+            _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
+
+            // TODO(b/291528545): On success, this should only be cleared after the view is animated
+            //  away).
+            clearInput()
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 15d1dae..782ead3 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -17,16 +17,19 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.content.Context
-import com.android.systemui.res.R
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import javax.inject.Inject
 import kotlin.math.ceil
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -35,6 +38,7 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.job
 import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input on bouncer UIs. */
@@ -44,8 +48,9 @@
 constructor(
     @Application private val applicationContext: Context,
     @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
     private val bouncerInteractor: BouncerInteractor,
-    private val authenticationInteractor: AuthenticationInteractor,
+    authenticationInteractor: AuthenticationInteractor,
     flags: SceneContainerFlags,
 ) {
     private val isInputEnabled: StateFlow<Boolean> =
@@ -57,91 +62,45 @@
                 initialValue = !bouncerInteractor.isThrottled.value,
             )
 
-    private val pin: PinBouncerViewModel by lazy {
-        PinBouncerViewModel(
-            applicationContext = applicationContext,
-            applicationScope = applicationScope,
-            interactor = bouncerInteractor,
-            isInputEnabled = isInputEnabled,
-        )
-    }
-
-    private val password: PasswordBouncerViewModel by lazy {
-        PasswordBouncerViewModel(
-            applicationScope = applicationScope,
-            interactor = bouncerInteractor,
-            isInputEnabled = isInputEnabled,
-        )
-    }
-
-    private val pattern: PatternBouncerViewModel by lazy {
-        PatternBouncerViewModel(
-            applicationContext = applicationContext,
-            applicationScope = applicationScope,
-            interactor = bouncerInteractor,
-            isInputEnabled = isInputEnabled,
-        )
-    }
-
     /** View-model for the current UI, based on the current authentication method. */
-    val authMethod: StateFlow<AuthMethodBouncerViewModel?> =
+    val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> =
         authenticationInteractor.authenticationMethod
-            .map { authenticationMethod ->
-                when (authenticationMethod) {
-                    is AuthenticationMethodModel.Pin -> pin
-                    is AuthenticationMethodModel.Password -> password
-                    is AuthenticationMethodModel.Pattern -> pattern
-                    else -> null
-                }
-            }
+            .map(::getChildViewModel)
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue = null,
             )
 
+    // Handle to the scope of the child ViewModel (stored in [authMethod]).
+    private var childViewModelScope: CoroutineScope? = null
+
     init {
         if (flags.isEnabled()) {
             applicationScope.launch {
-                bouncerInteractor.isThrottled
-                    .map { isThrottled ->
-                        if (isThrottled) {
-                            when (authenticationInteractor.getAuthenticationMethod()) {
-                                is AuthenticationMethodModel.Pin ->
-                                    R.string.kg_too_many_failed_pin_attempts_dialog_message
-                                is AuthenticationMethodModel.Password ->
-                                    R.string.kg_too_many_failed_password_attempts_dialog_message
-                                is AuthenticationMethodModel.Pattern ->
-                                    R.string.kg_too_many_failed_pattern_attempts_dialog_message
-                                else -> null
-                            }?.let { stringResourceId ->
-                                applicationContext.getString(
-                                    stringResourceId,
-                                    bouncerInteractor.throttling.value.failedAttemptCount,
-                                    ceil(bouncerInteractor.throttling.value.remainingMs / 1000f)
-                                        .toInt(),
-                                )
-                            }
+                combine(bouncerInteractor.isThrottled, authMethodViewModel) {
+                        isThrottled,
+                        authMethodViewModel ->
+                        if (isThrottled && authMethodViewModel != null) {
+                            applicationContext.getString(
+                                authMethodViewModel.throttlingMessageId,
+                                bouncerInteractor.throttling.value.failedAttemptCount,
+                                ceil(bouncerInteractor.throttling.value.remainingMs / 1000f)
+                                    .toInt(),
+                            )
                         } else {
                             null
                         }
                     }
                     .distinctUntilChanged()
-                    .collect { dialogMessageOrNull ->
-                        if (dialogMessageOrNull != null) {
-                            _throttlingDialogMessage.value = dialogMessageOrNull
-                        }
-                    }
+                    .collect { dialogMessage -> _throttlingDialogMessage.value = dialogMessage }
             }
         }
     }
 
     /** The user-facing message to show in the bouncer. */
     val message: StateFlow<MessageViewModel> =
-        combine(
-                bouncerInteractor.message,
-                bouncerInteractor.isThrottled,
-            ) { message, isThrottled ->
+        combine(bouncerInteractor.message, bouncerInteractor.isThrottled) { message, isThrottled ->
                 toMessageViewModel(message, isThrottled)
             }
             .stateIn(
@@ -186,6 +145,50 @@
         )
     }
 
+    private fun getChildViewModel(
+        authenticationMethod: AuthenticationMethodModel,
+    ): AuthMethodBouncerViewModel? {
+        // If the current child view-model matches the authentication method, reuse it instead of
+        // creating a new instance.
+        val childViewModel = authMethodViewModel.value
+        if (authenticationMethod == childViewModel?.authenticationMethod) {
+            return childViewModel
+        }
+
+        childViewModelScope?.cancel()
+        val newViewModelScope = createChildCoroutineScope(applicationScope)
+        childViewModelScope = newViewModelScope
+        return when (authenticationMethod) {
+            is AuthenticationMethodModel.Pin ->
+                PinBouncerViewModel(
+                    applicationContext = applicationContext,
+                    viewModelScope = newViewModelScope,
+                    interactor = bouncerInteractor,
+                    isInputEnabled = isInputEnabled,
+                )
+            is AuthenticationMethodModel.Password ->
+                PasswordBouncerViewModel(
+                    viewModelScope = newViewModelScope,
+                    interactor = bouncerInteractor,
+                    isInputEnabled = isInputEnabled,
+                )
+            is AuthenticationMethodModel.Pattern ->
+                PatternBouncerViewModel(
+                    applicationContext = applicationContext,
+                    viewModelScope = newViewModelScope,
+                    interactor = bouncerInteractor,
+                    isInputEnabled = isInputEnabled,
+                )
+            else -> null
+        }
+    }
+
+    private fun createChildCoroutineScope(parentScope: CoroutineScope): CoroutineScope {
+        return CoroutineScope(
+            SupervisorJob(parent = parentScope.coroutineContext.job) + mainDispatcher
+        )
+    }
+
     data class MessageViewModel(
         val text: String,
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 9e10f29..fe77419 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -16,22 +16,24 @@
 
 package com.android.systemui.bouncer.ui.viewmodel
 
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.res.R
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the password bouncer UI. */
 class PasswordBouncerViewModel(
-    private val applicationScope: CoroutineScope,
-    private val interactor: BouncerInteractor,
+    viewModelScope: CoroutineScope,
+    interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
 ) :
     AuthMethodBouncerViewModel(
-        isInputEnabled = isInputEnabled,
+        viewModelScope = viewModelScope,
         interactor = interactor,
+        isInputEnabled = isInputEnabled,
     ) {
 
     private val _password = MutableStateFlow("")
@@ -39,10 +41,16 @@
     /** The password entered so far. */
     val password: StateFlow<String> = _password.asStateFlow()
 
-    /** Notifies that the UI has been shown to the user. */
-    fun onShown() {
+    override val authenticationMethod = AuthenticationMethodModel.Password
+
+    override val throttlingMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
+
+    override fun clearInput() {
         _password.value = ""
-        interactor.resetMessage()
+    }
+
+    override fun getInput(): List<Any> {
+        return _password.value.toCharArray().toList()
     }
 
     /** Notifies that the user has changed the password input. */
@@ -60,17 +68,8 @@
 
     /** Notifies that the user has pressed the key for attempting to authenticate the password. */
     fun onAuthenticateKeyPressed() {
-        val password = _password.value.toCharArray().toList()
-        if (password.isEmpty()) {
-            return
-        }
-
-        _password.value = ""
-
-        applicationScope.launch {
-            if (interactor.authenticate(password) != true) {
-                showFailureAnimation()
-            }
+        if (_password.value.isNotEmpty()) {
+            tryAuthenticate()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 497276b..52adf54 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -18,8 +18,10 @@
 
 import android.content.Context
 import android.util.TypedValue
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.res.R
 import kotlin.math.max
 import kotlin.math.min
 import kotlin.math.pow
@@ -31,18 +33,18 @@
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the pattern bouncer UI. */
 class PatternBouncerViewModel(
     private val applicationContext: Context,
-    private val applicationScope: CoroutineScope,
-    private val interactor: BouncerInteractor,
+    viewModelScope: CoroutineScope,
+    interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
 ) :
     AuthMethodBouncerViewModel(
-        isInputEnabled = isInputEnabled,
+        viewModelScope = viewModelScope,
         interactor = interactor,
+        isInputEnabled = isInputEnabled,
     ) {
 
     /** The number of columns in the dot grid. */
@@ -58,7 +60,7 @@
         _selectedDots
             .map { it.toList() }
             .stateIn(
-                scope = applicationScope,
+                scope = viewModelScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue = emptyList(),
             )
@@ -76,15 +78,9 @@
     /** Whether the pattern itself should be rendered visibly. */
     val isPatternVisible: StateFlow<Boolean> = interactor.isPatternVisible
 
-    /** Notifies that the UI has been shown to the user. */
-    fun onShown() {
-        interactor.resetMessage()
-    }
+    override val authenticationMethod = AuthenticationMethodModel.Pattern
 
-    /** Notifies that the user has placed down a pointer, not necessarily dragging just yet. */
-    fun onDown() {
-        interactor.onDown()
-    }
+    override val throttlingMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message
 
     /** Notifies that the user has started a drag gesture across the dot grid. */
     fun onDragStart() {
@@ -164,24 +160,23 @@
 
     /** Notifies that the user has ended the drag gesture across the dot grid. */
     fun onDragEnd() {
-        val pattern = _selectedDots.value.map { it.toCoordinate() }
-
+        val pattern = getInput()
         if (pattern.size == 1) {
             // Single dot patterns are treated as erroneous/false taps:
             interactor.onFalseUserInput()
         }
 
+        tryAuthenticate()
+    }
+
+    override fun clearInput() {
         _dots.value = defaultDots()
         _currentDot.value = null
         _selectedDots.value = linkedSetOf()
+    }
 
-        applicationScope.launch {
-            if (pattern.size < interactor.minPatternLength) {
-                interactor.showErrorMessage()
-            } else if (interactor.authenticate(pattern) != true) {
-                showFailureAnimation()
-            }
-        }
+    override fun getInput(): List<Any> {
+        return _selectedDots.value.map(PatternDotViewModel::toCoordinate)
     }
 
     private fun defaultDots(): List<PatternDotViewModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 8e6421e..b90e255 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -18,7 +18,9 @@
 
 import android.content.Context
 import com.android.keyguard.PinShapeAdapter
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.res.R
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
@@ -26,18 +28,18 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the PIN code bouncer UI. */
 class PinBouncerViewModel(
     applicationContext: Context,
-    private val applicationScope: CoroutineScope,
-    private val interactor: BouncerInteractor,
+    viewModelScope: CoroutineScope,
+    interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
 ) :
     AuthMethodBouncerViewModel(
-        isInputEnabled = isInputEnabled,
+        viewModelScope = viewModelScope,
         interactor = interactor,
+        isInputEnabled = isInputEnabled,
     ) {
 
     val pinShapes = PinShapeAdapter(applicationContext)
@@ -61,7 +63,7 @@
                 )
             }
             .stateIn(
-                scope = applicationScope,
+                scope = viewModelScope,
                 // Make sure this is kept as WhileSubscribed or we can run into a bug where the
                 // downstream continues to receive old/stale/cached values.
                 started = SharingStarted.WhileSubscribed(),
@@ -73,21 +75,14 @@
         interactor.isAutoConfirmEnabled
             .map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown }
             .stateIn(
-                scope = applicationScope,
+                scope = viewModelScope,
                 started = SharingStarted.Eagerly,
                 initialValue = ActionButtonAppearance.Hidden,
             )
 
-    /** Notifies that the UI has been shown to the user. */
-    fun onShown() {
-        clearPinInput()
-        interactor.resetMessage()
-    }
+    override val authenticationMethod = AuthenticationMethodModel.Pin
 
-    /** Notifies that the user has placed down a pointer. */
-    fun onDown() {
-        interactor.onDown()
-    }
+    override val throttlingMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message
 
     /** Notifies that the user clicked on a PIN button with the given digit value. */
     fun onPinButtonClicked(input: Int) {
@@ -109,7 +104,8 @@
 
     /** Notifies that the user long-pressed the backspace button. */
     fun onBackspaceButtonLongPressed() {
-        clearPinInput()
+        clearInput()
+        interactor.clearMessage()
     }
 
     /** Notifies that the user clicked the "enter" button. */
@@ -117,24 +113,12 @@
         tryAuthenticate(useAutoConfirm = false)
     }
 
-    private fun clearPinInput() {
+    override fun clearInput() {
         mutablePinInput.value = mutablePinInput.value.clearAll()
     }
 
-    private fun tryAuthenticate(useAutoConfirm: Boolean) {
-        val pinCode = mutablePinInput.value.getPin()
-
-        applicationScope.launch {
-            val isSuccess = interactor.authenticate(pinCode, useAutoConfirm) ?: return@launch
-
-            if (!isSuccess) {
-                showFailureAnimation()
-            }
-
-            // TODO(b/291528545): this should not be cleared on success (at least until the view
-            // is animated away).
-            clearPinInput()
-        }
+    override fun getInput(): List<Any> {
+        return mutablePinInput.value.getPin()
     }
 
     private fun computeBackspaceButtonAppearance(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index fc7d20a..874053a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -236,7 +236,8 @@
             utils.authenticationRepository.setAuthenticationMethod(
                 DataLayerAuthenticationMethodModel.Pin
             )
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(isThrottled).isFalse()
         }
 
@@ -246,7 +247,8 @@
             utils.authenticationRepository.setAuthenticationMethod(
                 DataLayerAuthenticationMethodModel.Pin
             )
-            assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))).isFalse()
+            assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4)))
+                .isEqualTo(AuthenticationResult.FAILED)
         }
 
     @Test(expected = IllegalArgumentException::class)
@@ -267,7 +269,7 @@
                 overrideCredential(pin)
             }
 
-            assertThat(underTest.authenticate(pin)).isTrue()
+            assertThat(underTest.authenticate(pin)).isEqualTo(AuthenticationResult.SUCCEEDED)
         }
 
     @Test
@@ -282,7 +284,8 @@
             utils.authenticationRepository.setAuthenticationMethod(
                 DataLayerAuthenticationMethodModel.Pin
             )
-            assertThat(underTest.authenticate(List(17) { 9 })).isFalse()
+            assertThat(underTest.authenticate(List(17) { 9 }))
+                .isEqualTo(AuthenticationResult.FAILED)
         }
 
     @Test
@@ -293,7 +296,8 @@
                 DataLayerAuthenticationMethodModel.Password
             )
 
-            assertThat(underTest.authenticate("password".toList())).isTrue()
+            assertThat(underTest.authenticate("password".toList()))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(isThrottled).isFalse()
         }
 
@@ -304,7 +308,8 @@
                 DataLayerAuthenticationMethodModel.Password
             )
 
-            assertThat(underTest.authenticate("alohomora".toList())).isFalse()
+            assertThat(underTest.authenticate("alohomora".toList()))
+                .isEqualTo(AuthenticationResult.FAILED)
         }
 
     @Test
@@ -314,7 +319,8 @@
                 DataLayerAuthenticationMethodModel.Pattern
             )
 
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
         }
 
     @Test
@@ -327,22 +333,14 @@
             assertThat(
                     underTest.authenticate(
                         listOf(
-                            AuthenticationPatternCoordinate(
-                                x = 2,
-                                y = 0,
-                            ),
-                            AuthenticationPatternCoordinate(
-                                x = 2,
-                                y = 1,
-                            ),
-                            AuthenticationPatternCoordinate(
-                                x = 2,
-                                y = 2,
-                            ),
+                            AuthenticationPatternCoordinate(x = 2, y = 0),
+                            AuthenticationPatternCoordinate(x = 2, y = 1),
+                            AuthenticationPatternCoordinate(x = 2, y = 2),
+                            AuthenticationPatternCoordinate(x = 1, y = 2),
                         )
                     )
                 )
-                .isFalse()
+                .isEqualTo(AuthenticationResult.FAILED)
         }
 
     @Test
@@ -361,7 +359,7 @@
                         tryAutoConfirm = true
                     )
                 )
-                .isNull()
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(isThrottled).isFalse()
         }
 
@@ -379,7 +377,7 @@
                         tryAutoConfirm = true
                     )
                 )
-                .isFalse()
+                .isEqualTo(AuthenticationResult.FAILED)
             assertThat(isUnlocked).isFalse()
         }
 
@@ -397,7 +395,7 @@
                         tryAutoConfirm = true
                     )
                 )
-                .isFalse()
+                .isEqualTo(AuthenticationResult.FAILED)
             assertThat(isUnlocked).isFalse()
         }
 
@@ -415,7 +413,7 @@
                         tryAutoConfirm = true
                     )
                 )
-                .isTrue()
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(isUnlocked).isTrue()
         }
 
@@ -433,7 +431,7 @@
                         tryAutoConfirm = true
                     )
                 )
-                .isNull()
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(isUnlocked).isFalse()
         }
 
@@ -445,7 +443,8 @@
                 DataLayerAuthenticationMethodModel.Password
             )
 
-            assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull()
+            assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true))
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(isUnlocked).isFalse()
         }
 
@@ -490,7 +489,8 @@
                 )
 
             // Correct PIN, but throttled, so doesn't attempt it:
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isNull()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(isUnlocked).isFalse()
             assertThat(isThrottled).isTrue()
             assertThat(throttling)
@@ -536,7 +536,8 @@
                 )
 
             // Correct PIN and no longer throttled so unlocks successfully:
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(isUnlocked).isTrue()
             assertThat(isThrottled).isFalse()
             assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 77d8102..c0257df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -17,13 +17,14 @@
 package com.android.systemui.bouncer.domain.interactor
 
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.AuthenticationResult
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -87,7 +88,8 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
 
             // Wrong input.
-            assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
+            assertThat(underTest.authenticate(listOf(9, 8, 7)))
+                .isEqualTo(AuthenticationResult.FAILED)
             assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
@@ -95,7 +97,8 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
 
             // Correct input.
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
@@ -115,13 +118,14 @@
             underTest.clearMessage()
 
             // Incomplete input.
-            assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true)).isNull()
+            assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true))
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(message).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Wrong 6-digit pin
             assertThat(underTest.authenticate(listOf(1, 2, 3, 5, 5, 6), tryAutoConfirm = true))
-                .isFalse()
+                .isEqualTo(AuthenticationResult.FAILED)
             assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
@@ -132,7 +136,7 @@
                         tryAutoConfirm = true
                     )
                 )
-                .isTrue()
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
@@ -150,7 +154,8 @@
             underTest.clearMessage()
 
             // Incomplete input.
-            assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true)).isNull()
+            assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true))
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(message).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
@@ -161,7 +166,7 @@
                         tryAutoConfirm = true
                     )
                 )
-                .isNull()
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(message).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
@@ -187,7 +192,8 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
 
             // Wrong input.
-            assertThat(underTest.authenticate("alohamora".toList())).isFalse()
+            assertThat(underTest.authenticate("alohamora".toList()))
+                .isEqualTo(AuthenticationResult.FAILED)
             assertThat(message).isEqualTo(MESSAGE_WRONG_PASSWORD)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
@@ -195,7 +201,8 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
 
             // Correct input.
-            assertThat(underTest.authenticate("password".toList())).isTrue()
+            assertThat(underTest.authenticate("password".toList()))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
@@ -220,8 +227,30 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
 
             // Wrong input.
-            assertThat(underTest.authenticate(listOf(AuthenticationPatternCoordinate(1, 2))))
-                .isFalse()
+            val wrongPattern =
+                listOf(
+                    AuthenticationPatternCoordinate(1, 2),
+                    AuthenticationPatternCoordinate(1, 1),
+                    AuthenticationPatternCoordinate(0, 0),
+                    AuthenticationPatternCoordinate(0, 1),
+                )
+            assertThat(wrongPattern).isNotEqualTo(FakeAuthenticationRepository.PATTERN)
+            assertThat(wrongPattern.size).isAtLeast(utils.authenticationRepository.minPatternLength)
+            assertThat(underTest.authenticate(wrongPattern)).isEqualTo(AuthenticationResult.FAILED)
+            assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+            underTest.resetMessage()
+            assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
+
+            // Too short input.
+            val tooShortPattern =
+                FakeAuthenticationRepository.PATTERN.subList(
+                    0,
+                    utils.authenticationRepository.minPatternLength - 1
+                )
+            assertThat(underTest.authenticate(tooShortPattern))
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
@@ -229,7 +258,8 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
 
             // Correct input.
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
@@ -294,7 +324,8 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times ->
                 // Wrong PIN.
-                assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse()
+                assertThat(underTest.authenticate(listOf(6, 7, 8, 9)))
+                    .isEqualTo(AuthenticationResult.FAILED)
                 if (
                     times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
                 ) {
@@ -317,7 +348,8 @@
             )
 
             // Correct PIN, but throttled, so doesn't change away from the bouncer scene:
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isNull()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
             assertTryAgainMessage(
                 message,
@@ -347,7 +379,8 @@
             assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
 
             // Correct PIN and no longer throttled so changes to the Gone scene:
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(currentScene?.key).isEqualTo(SceneKey.Gone)
             assertThat(isThrottled).isFalse()
             assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 9011c2f..2f7dde0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -45,7 +45,7 @@
     private val underTest =
         PinBouncerViewModel(
             applicationContext = context,
-            applicationScope = testScope.backgroundScope,
+            viewModelScope = testScope.backgroundScope,
             interactor =
                 utils.bouncerInteractor(
                     authenticationInteractor = authenticationInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 2c96bcc..da2534d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -46,7 +46,7 @@
     private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
-            repository = utils.authenticationRepository(),
+            repository = utils.authenticationRepository,
         )
     private val bouncerInteractor =
         utils.bouncerInteractor(
@@ -66,7 +66,8 @@
 
             authMethodsToTest().forEach { authMethod ->
                 utils.authenticationRepository.setAuthenticationMethod(authMethod)
-                val job = underTest.authMethod.onEach { authMethodViewModel = it }.launchIn(this)
+                val job =
+                    underTest.authMethodViewModel.onEach { authMethodViewModel = it }.launchIn(this)
                 runCurrent()
 
                 if (authMethod.isSecure) {
@@ -86,22 +87,43 @@
         }
 
     @Test
-    fun authMethod_reusesInstances() =
+    fun authMethodChanged_doesNotReuseInstances() =
         testScope.runTest {
             val seen =
                 mutableMapOf<DomainLayerAuthenticationMethodModel, AuthMethodBouncerViewModel>()
             val authMethodViewModel: AuthMethodBouncerViewModel? by
-                collectLastValue(underTest.authMethod)
+                collectLastValue(underTest.authMethodViewModel)
+
             // First pass, populate our "seen" map:
             authMethodsToTest().forEach { authMethod ->
                 utils.authenticationRepository.setAuthenticationMethod(authMethod)
                 authMethodViewModel?.let { seen[authMethod] = it }
             }
 
-            // Second pass, assert same instances are reused:
+            // Second pass, assert same instances are not reused:
             authMethodsToTest().forEach { authMethod ->
                 utils.authenticationRepository.setAuthenticationMethod(authMethod)
-                authMethodViewModel?.let { assertThat(it).isSameInstanceAs(seen[authMethod]) }
+                authMethodViewModel?.let {
+                    assertThat(it.authenticationMethod).isEqualTo(authMethod)
+                    assertThat(it).isNotSameInstanceAs(seen[authMethod])
+                }
+            }
+        }
+
+    @Test
+    fun authMethodUnchanged_reusesInstances() =
+        testScope.runTest {
+            authMethodsToTest().forEach { authMethod ->
+                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                val firstInstance: AuthMethodBouncerViewModel? =
+                    collectLastValue(underTest.authMethodViewModel).invoke()
+
+                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                val secondInstance: AuthMethodBouncerViewModel? =
+                    collectLastValue(underTest.authMethodViewModel).invoke()
+
+                firstInstance?.let { assertThat(it.authenticationMethod).isEqualTo(authMethod) }
+                assertThat(secondInstance).isSameInstanceAs(firstInstance)
             }
         }
 
@@ -136,7 +158,7 @@
         testScope.runTest {
             val isInputEnabled by
                 collectLastValue(
-                    underTest.authMethod.flatMapLatest { authViewModel ->
+                    underTest.authMethodViewModel.flatMapLatest { authViewModel ->
                         authViewModel?.isInputEnabled ?: emptyFlow()
                     }
                 )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 3375184..c1b3354 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -17,10 +17,11 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainAuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -28,6 +29,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -59,7 +61,7 @@
         )
     private val underTest =
         PasswordBouncerViewModel(
-            applicationScope = testScope.backgroundScope,
+            viewModelScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
         )
@@ -76,19 +78,13 @@
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-
-            underTest.onShown()
+            lockDeviceAndOpenPasswordBouncer()
 
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
             assertThat(password).isEqualTo("")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            assertThat(underTest.authenticationMethod)
+                .isEqualTo(DomainAuthenticationMethodModel.Password)
         }
 
     @Test
@@ -97,15 +93,7 @@
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
-            runCurrent()
+            lockDeviceAndOpenPasswordBouncer()
 
             underTest.onPasswordInputChanged("password")
 
@@ -118,16 +106,9 @@
     fun onAuthenticateKeyPressed_whenCorrect() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
-            underTest.onPasswordInputChanged("password")
+            lockDeviceAndOpenPasswordBouncer()
 
+            underTest.onPasswordInputChanged("password")
             underTest.onAuthenticateKeyPressed()
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
@@ -139,16 +120,9 @@
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
-            underTest.onPasswordInputChanged("wrong")
+            lockDeviceAndOpenPasswordBouncer()
 
+            underTest.onPasswordInputChanged("wrong")
             underTest.onAuthenticateKeyPressed()
 
             assertThat(password).isEqualTo("")
@@ -185,14 +159,9 @@
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
+            lockDeviceAndOpenPasswordBouncer()
+
+            // Enter the wrong password:
             underTest.onPasswordInputChanged("wrong")
             underTest.onAuthenticateKeyPressed()
             assertThat(password).isEqualTo("")
@@ -213,14 +182,7 @@
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val password by collectLastValue(underTest.password)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
+            lockDeviceAndOpenPasswordBouncer()
 
             // The user types a password.
             underTest.onPasswordInputChanged("password")
@@ -243,6 +205,18 @@
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
+    private fun TestScope.lockDeviceAndOpenPasswordBouncer() {
+        utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password)
+        utils.authenticationRepository.setUnlocked(false)
+        sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+        sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
+        assertThat(collectLastValue(sceneInteractor.desiredScene).invoke())
+            .isEqualTo(SceneModel(SceneKey.Bouncer))
+        underTest.onShown()
+        runCurrent()
+    }
+
     companion object {
         private const val ENTER_YOUR_PASSWORD = "Enter your password"
         private const val WRONG_PASSWORD = "Wrong password"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 102cfe2..bf109d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -17,12 +17,13 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainAuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -64,7 +65,7 @@
     private val underTest =
         PatternBouncerViewModel(
             applicationContext = context,
-            applicationScope = testScope.backgroundScope,
+            viewModelScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
         )
@@ -85,14 +86,14 @@
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            transitionToPatternBouncer()
-
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
 
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PATTERN)
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            assertThat(underTest.authenticationMethod)
+                .isEqualTo(DomainAuthenticationMethodModel.Pattern)
         }
 
     @Test
@@ -102,9 +103,7 @@
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            transitionToPatternBouncer()
-            underTest.onShown()
-            runCurrent()
+            lockDeviceAndOpenPatternBouncer()
 
             underTest.onDragStart()
 
@@ -120,8 +119,7 @@
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
             underTest.onDragStart()
             assertThat(currentDot).isNull()
             CORRECT_PATTERN.forEachIndexed { index, coordinate ->
@@ -158,8 +156,7 @@
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
             underTest.onDragStart()
             CORRECT_PATTERN.subList(0, 3).forEach { coordinate -> dragToCoordinate(coordinate) }
 
@@ -175,8 +172,7 @@
     fun onDrag_shouldIncludeDotsThatWereSkippedOverAlongTheSameRow() =
         testScope.runTest {
             val selectedDots by collectLastValue(underTest.selectedDots)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
 
             /*
              * Pattern setup, coordinates are (column, row)
@@ -202,8 +198,7 @@
     fun onDrag_shouldIncludeDotsThatWereSkippedOverAlongTheSameColumn() =
         testScope.runTest {
             val selectedDots by collectLastValue(underTest.selectedDots)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
 
             /*
              * Pattern setup, coordinates are (column, row)
@@ -229,8 +224,7 @@
     fun onDrag_shouldIncludeDotsThatWereSkippedOverAlongTheDiagonal() =
         testScope.runTest {
             val selectedDots by collectLastValue(underTest.selectedDots)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
 
             /*
              * Pattern setup
@@ -258,8 +252,7 @@
     fun onDrag_shouldNotIncludeDotIfItIsNotOnTheLine() =
         testScope.runTest {
             val selectedDots by collectLastValue(underTest.selectedDots)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
 
             /*
              * Pattern setup
@@ -287,8 +280,7 @@
     fun onDrag_shouldNotIncludeSkippedOverDotsIfTheyAreAlreadySelected() =
         testScope.runTest {
             val selectedDots by collectLastValue(underTest.selectedDots)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
 
             /*
              * Pattern setup
@@ -315,20 +307,10 @@
     @Test
     fun onDragEnd_whenPatternTooShort() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
-            val selectedDots by collectLastValue(underTest.selectedDots)
-            val currentDot by collectLastValue(underTest.currentDot)
             val throttlingDialogMessage by
                 collectLastValue(bouncerViewModel.throttlingDialogMessage)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern
-            )
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
 
             // Enter a pattern that's too short more than enough times that would normally trigger
             // throttling if the pattern were not too short and wrong:
@@ -337,7 +319,7 @@
                 underTest.onDragStart()
                 CORRECT_PATTERN.subList(
                         0,
-                        authenticationInteractor.minPatternLength - 1,
+                        utils.authenticationRepository.minPatternLength - 1,
                     )
                     .forEach { coordinate ->
                         underTest.onDrag(
@@ -362,10 +344,10 @@
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
+
             underTest.onDragStart()
-            CORRECT_PATTERN.subList(2, 7).forEach { coordinate -> dragToCoordinate(coordinate) }
+            CORRECT_PATTERN.subList(2, 7).forEach(::dragToCoordinate)
             underTest.onDragEnd()
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
@@ -373,7 +355,7 @@
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Enter the correct pattern:
-            CORRECT_PATTERN.forEach { coordinate -> dragToCoordinate(coordinate) }
+            CORRECT_PATTERN.forEach(::dragToCoordinate)
 
             underTest.onDragEnd()
 
@@ -382,7 +364,7 @@
 
     private fun dragOverCoordinates(vararg coordinatesDragged: Point) {
         underTest.onDragStart()
-        coordinatesDragged.forEach { dragToCoordinate(it) }
+        coordinatesDragged.forEach(::dragToCoordinate)
     }
 
     private fun dragToCoordinate(coordinate: Point) {
@@ -394,13 +376,15 @@
         )
     }
 
-    private fun TestScope.transitionToPatternBouncer() {
+    private fun TestScope.lockDeviceAndOpenPatternBouncer() {
         utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern)
         utils.authenticationRepository.setUnlocked(false)
         sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
         sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
         assertThat(collectLastValue(sceneInteractor.desiredScene).invoke())
             .isEqualTo(SceneModel(SceneKey.Bouncer))
+        underTest.onShown()
+        runCurrent()
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 35238ce..2576204 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -17,11 +17,12 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainAuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -30,6 +31,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -62,7 +64,7 @@
     private val underTest =
         PinBouncerViewModel(
             applicationContext = context,
-            applicationScope = testScope.backgroundScope,
+            viewModelScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
         )
@@ -90,6 +92,8 @@
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN)
             assertThat(pin).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            assertThat(underTest.authenticationMethod)
+                .isEqualTo(DomainAuthenticationMethodModel.Pin)
         }
 
     @Test
@@ -120,14 +124,8 @@
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            lockDeviceAndOpenPinBouncer()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
-            runCurrent()
             underTest.onPinButtonClicked(1)
             assertThat(pin).hasSize(1)
 
@@ -141,15 +139,8 @@
     @Test
     fun onPinEdit() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
+            lockDeviceAndOpenPinBouncer()
 
             underTest.onPinButtonClicked(1)
             underTest.onPinButtonClicked(2)
@@ -168,18 +159,13 @@
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            lockDeviceAndOpenPinBouncer()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
-            runCurrent()
             underTest.onPinButtonClicked(1)
             underTest.onPinButtonClicked(2)
             underTest.onPinButtonClicked(3)
             underTest.onPinButtonClicked(4)
+            runCurrent()
 
             underTest.onBackspaceButtonLongPressed()
 
@@ -192,13 +178,8 @@
     fun onAuthenticateButtonClicked_whenCorrect() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            lockDeviceAndOpenPinBouncer()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
             FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
                 underTest.onPinButtonClicked(digit)
             }
@@ -214,13 +195,8 @@
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            lockDeviceAndOpenPinBouncer()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
             underTest.onPinButtonClicked(1)
             underTest.onPinButtonClicked(2)
             underTest.onPinButtonClicked(3)
@@ -240,13 +216,8 @@
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            lockDeviceAndOpenPinBouncer()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
             underTest.onPinButtonClicked(1)
             underTest.onPinButtonClicked(2)
             underTest.onPinButtonClicked(3)
@@ -272,14 +243,9 @@
     fun onAutoConfirm_whenCorrect() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
             utils.authenticationRepository.setAutoConfirmEnabled(true)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            lockDeviceAndOpenPinBouncer()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
             FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
                 underTest.onPinButtonClicked(digit)
             }
@@ -293,14 +259,9 @@
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
             utils.authenticationRepository.setAutoConfirmEnabled(true)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            lockDeviceAndOpenPinBouncer()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
             FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
                 underTest.onPinButtonClicked(digit)
             }
@@ -318,13 +279,7 @@
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
+            lockDeviceAndOpenPinBouncer()
 
             // The user types a PIN.
             FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
@@ -401,6 +356,18 @@
             assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
         }
 
+    private fun TestScope.lockDeviceAndOpenPinBouncer() {
+        utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+        utils.authenticationRepository.setUnlocked(false)
+        sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+        sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
+        assertThat(collectLastValue(sceneInteractor.desiredScene).invoke())
+            .isEqualTo(SceneModel(SceneKey.Bouncer))
+        underTest.onShown()
+        runCurrent()
+    }
+
     companion object {
         private const val ENTER_YOUR_PIN = "Enter your pin"
         private const val WRONG_PIN = "Wrong pin"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 6b918c6..85bd92b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -76,6 +76,7 @@
  *   being used when the state is as required (e.g. cannot unlock an already unlocked device, cannot
  *   put to sleep a device that's already asleep, etc.).
  */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class SceneFrameworkIntegrationTest : SysuiTestCase() {
@@ -481,7 +482,7 @@
         bouncerSceneJob =
             if (to.key == SceneKey.Bouncer) {
                 testScope.backgroundScope.launch {
-                    bouncerViewModel.authMethod.collect {
+                    bouncerViewModel.authMethodViewModel.collect {
                         // Do nothing. Need this to turn this otherwise cold flow, hot.
                     }
                 }
@@ -556,7 +557,7 @@
         assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
             .that(getCurrentSceneInUi())
             .isEqualTo(SceneKey.Bouncer)
-        val authMethodViewModel by collectLastValue(bouncerViewModel.authMethod)
+        val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel)
         assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
             .that(authMethodViewModel)
             .isInstanceOf(PinBouncerViewModel::class.java)
@@ -613,11 +614,12 @@
     private fun TestScope.dismissIme(
         showImeBeforeDismissing: Boolean = true,
     ) {
-        if (showImeBeforeDismissing) {
-            bouncerViewModel.authMethod.value?.onImeVisibilityChanged(true)
+        bouncerViewModel.authMethodViewModel.value?.apply {
+            if (showImeBeforeDismissing) {
+                onImeVisibilityChanged(true)
+            }
+            onImeVisibilityChanged(false)
+            runCurrent()
         }
-
-        bouncerViewModel.authMethod.value?.onImeVisibilityChanged(false)
-        runCurrent()
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 1620dc27..69c89e8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -206,6 +206,7 @@
         return BouncerViewModel(
             applicationContext = context,
             applicationScope = applicationScope(),
+            mainDispatcher = testDispatcher,
             bouncerInteractor = bouncerInteractor,
             authenticationInteractor = authenticationInteractor,
             flags = sceneContainerFlags,
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 2ca84f8..30b9d0b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -40,6 +40,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -166,9 +167,12 @@
 
     @VisibleForTesting boolean mIsSinglePanningEnabled;
 
-    /**
-     * FullScreenMagnificationGestureHandler Constructor.
-     */
+    private final FullScreenMagnificationVibrationHelper mFullScreenMagnificationVibrationHelper;
+
+    @VisibleForTesting final OverscrollHandler mOverscrollHandler;
+
+    private final boolean mIsWatch;
+
     public FullScreenMagnificationGestureHandler(@UiContext Context context,
             FullScreenMagnificationController fullScreenMagnificationController,
             AccessibilityTraceManager trace,
@@ -254,11 +258,13 @@
         mDetectingState = new DetectingState(context);
         mViewportDraggingState = new ViewportDraggingState();
         mPanningScalingState = new PanningScalingState(context);
-        mSinglePanningState = new SinglePanningState(context,
-                fullScreenMagnificationVibrationHelper);
+        mSinglePanningState = new SinglePanningState(context);
+        mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper;
         setSinglePanningEnabled(
                 context.getResources()
                         .getBoolean(R.bool.config_enable_a11y_magnification_single_panning));
+        mOverscrollHandler = new OverscrollHandler();
+        mIsWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
 
         if (mDetectShortcutTrigger) {
             mScreenStateReceiver = new ScreenStateReceiver(context, this);
@@ -468,15 +474,21 @@
         @Override
         public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
             int action = event.getActionMasked();
-
             if (action == ACTION_POINTER_UP
                     && event.getPointerCount() == 2 // includes the pointer currently being released
                     && mPreviousState == mViewportDraggingState) {
-
+                // if feature flag is enabled, currently only true on watches
+                if (mIsSinglePanningEnabled) {
+                    mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
+                    mOverscrollHandler.clearEdgeState();
+                }
                 persistScaleAndTransitionTo(mViewportDraggingState);
-
             } else if (action == ACTION_UP || action == ACTION_CANCEL) {
-
+                // if feature flag is enabled, currently only true on watches
+                if (mIsSinglePanningEnabled) {
+                    mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
+                    mOverscrollHandler.clearEdgeState();
+                }
                 persistScaleAndTransitionTo(mDetectingState);
             }
         }
@@ -502,7 +514,12 @@
         }
 
         public void persistScaleAndTransitionTo(State state) {
-            mFullScreenMagnificationController.persistScale(mDisplayId);
+            // If device is a watch don't change user settings scale. On watches, warp effect
+            // is enabled and the current display scale could be differ from the default user
+            // settings scale (should not change the scale due to the warp effect)
+            if (!mIsWatch) {
+                mFullScreenMagnificationController.persistScale(mDisplayId);
+            }
             clear();
             transitionTo(state);
         }
@@ -546,6 +563,9 @@
             }
             mFullScreenMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
                     distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+            if (mIsSinglePanningEnabled) {
+                mOverscrollHandler.onScrollStateChanged(first, second);
+            }
             return /* event consumed: */ true;
         }
 
@@ -893,6 +913,11 @@
                         if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) {
                             transitionToViewportDraggingStateAndClear(event);
                         } else if (isActivated() && event.getPointerCount() == 2) {
+                            if (mIsSinglePanningEnabled
+                                    && overscrollState(event, mFirstPointerDownLocation)
+                                    == OVERSCROLL_VERTICAL_EDGE) {
+                                transitionToDelegatingStateAndClear();
+                            }
                             //Primary pointer is swiping, so transit to PanningScalingState
                             transitToPanningScalingStateAndClear();
                         } else if (mIsSinglePanningEnabled
@@ -1264,6 +1289,7 @@
                 + ", mMagnificationController=" + mFullScreenMagnificationController
                 + ", mDisplayId=" + mDisplayId
                 + ", mIsSinglePanningEnabled=" + mIsSinglePanningEnabled
+                + ", mOverscrollHandler=" + mOverscrollHandler
                 + '}';
     }
 
@@ -1411,32 +1437,8 @@
 
         private final GestureDetector mScrollGestureDetector;
         private MotionEventInfo mEvent;
-        private final FullScreenMagnificationVibrationHelper
-                mFullScreenMagnificationVibrationHelper;
-
-        @VisibleForTesting int mOverscrollState;
-
-        // mPivotEdge is the point on the edge of the screen when the magnified view hits the edge
-        // This point sets the center of magnified view when warp/scale effect is triggered
-        private final PointF mPivotEdge;
-
-        // mReachedEdgeCoord is the user's pointer location on the screen when the magnified view
-        // has hit the edge
-        private final PointF mReachedEdgeCoord;
-        // mEdgeCooldown value will be set to true when user hits the edge and will be set to false
-        // once the user moves x distance away from the edge. This is so that vibrating haptic
-        // doesn't get triggered by slight movements
-        private boolean mEdgeCooldown;
-
-        SinglePanningState(
-                Context context,
-                FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) {
+        SinglePanningState(Context context) {
             mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain());
-            mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper;
-            mOverscrollState = OVERSCROLL_NONE;
-            mPivotEdge = new PointF(Float.NaN, Float.NaN);
-            mReachedEdgeCoord = new PointF(Float.NaN, Float.NaN);
-            mEdgeCooldown = false;
         }
 
         @Override
@@ -1445,17 +1447,8 @@
             switch (action) {
                 case ACTION_UP:
                 case ACTION_CANCEL:
-                    if (mOverscrollState == OVERSCROLL_LEFT_EDGE
-                            || mOverscrollState == OVERSCROLL_RIGHT_EDGE) {
-                        mFullScreenMagnificationController.setScaleAndCenter(
-                                mDisplayId,
-                                mFullScreenMagnificationController.getPersistedScale(mDisplayId),
-                                mPivotEdge.x,
-                                mPivotEdge.y,
-                                true,
-                                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-                    }
-                    clear();
+                    mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
+                    mOverscrollHandler.clearEdgeState();
                     transitionTo(mDetectingState);
                     break;
             }
@@ -1482,23 +1475,7 @@
                                 + " isAtEdge: "
                                 + mFullScreenMagnificationController.isAtEdge(mDisplayId));
             }
-            if (mFullScreenMagnificationController.isAtEdge(mDisplayId)) {
-                playEdgeVibration(second);
-                setPivotEdge();
-            }
-            if (mOverscrollState == OVERSCROLL_NONE) {
-                mOverscrollState = overscrollState(second, new PointF(first.getX(), first.getY()));
-            } else if (mOverscrollState == OVERSCROLL_VERTICAL_EDGE) {
-                clear();
-                transitionTo(mDelegatingState);
-            } else {
-                boolean reset = warpEffectReset(second);
-                if (reset) {
-                    mFullScreenMagnificationController.reset(mDisplayId, /* animate */ true);
-                    clear();
-                    transitionTo(mDetectingState);
-                }
-            }
+            mOverscrollHandler.onScrollStateChanged(first, second);
             return /* event consumed: */ true;
         }
 
@@ -1515,35 +1492,37 @@
         public String toString() {
             return "SinglePanningState{"
                     + "isEdgeOfView="
-                    + mFullScreenMagnificationController.isAtEdge(mDisplayId)
-                    + "overscrollStatus="
-                    + mOverscrollState
-                    + "}";
+                    + mFullScreenMagnificationController.isAtEdge(mDisplayId);
         }
 
-        private void playEdgeVibration(MotionEvent event) {
-            if (mOverscrollState == OVERSCROLL_NONE) {
-                vibrateIfNeeded(event);
-            }
+    }
+
+    /** Overscroll Handler handles the logic when user is at the edge and scrolls past an edge */
+    final class OverscrollHandler {
+
+        @VisibleForTesting int mOverscrollState;
+
+        // mPivotEdge is the point on the edge of the screen when the magnified view hits the edge
+        // This point sets the center of magnified view when warp/scale effect is triggered
+        private final PointF mPivotEdge;
+
+        // mReachedEdgeCoord is the user's pointer location on the screen when the magnified view
+        // has hit the edge
+        private final PointF mReachedEdgeCoord;
+
+        // mEdgeCooldown value will be set to true when user hits the edge and will be set to false
+        // once the user moves x distance away from the edge. This is so that vibrating haptic
+        // doesn't get triggered by slight movements
+        private boolean mEdgeCooldown;
+
+        OverscrollHandler() {
+            mOverscrollState = OVERSCROLL_NONE;
+            mPivotEdge = new PointF(Float.NaN, Float.NaN);
+            mReachedEdgeCoord = new PointF(Float.NaN, Float.NaN);
+            mEdgeCooldown = false;
         }
 
-        private void setPivotEdge() {
-            if (!pointerValid(mPivotEdge)) {
-                Rect bounds = new Rect();
-                mFullScreenMagnificationController.getMagnificationBounds(mDisplayId, bounds);
-                if (mOverscrollState == OVERSCROLL_LEFT_EDGE) {
-                    mPivotEdge.set(
-                            bounds.left,
-                            mFullScreenMagnificationController.getCenterY(mDisplayId));
-                } else if (mOverscrollState == OVERSCROLL_RIGHT_EDGE) {
-                    mPivotEdge.set(
-                            bounds.right,
-                            mFullScreenMagnificationController.getCenterY(mDisplayId));
-                }
-            }
-        }
-
-        private boolean warpEffectReset(MotionEvent second) {
+        protected boolean warpEffectReset(MotionEvent second) {
             float scale = calculateOverscrollScale(second);
             if (scale < 0) return false;
             mFullScreenMagnificationController.setScaleAndCenter(
@@ -1566,7 +1545,7 @@
             float overshootDistX = second.getX() - mReachedEdgeCoord.x;
             if ((mOverscrollState == OVERSCROLL_LEFT_EDGE && overshootDistX < 0)
                     || (mOverscrollState == OVERSCROLL_RIGHT_EDGE && overshootDistX > 0)) {
-                clear();
+                clearEdgeState();
                 return -1.0f;
             }
             float overshootDistY = second.getY() - mReachedEdgeCoord.y;
@@ -1611,21 +1590,109 @@
         }
 
         private void vibrateIfNeeded(MotionEvent event) {
+            if (mOverscrollState != OVERSCROLL_NONE) {
+                return;
+            }
             if ((mFullScreenMagnificationController.isAtLeftEdge(mDisplayId)
                             || mFullScreenMagnificationController.isAtRightEdge(mDisplayId))
                     && !mEdgeCooldown) {
                 mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled();
+            }
+        }
+
+        private void setPivotEdge(MotionEvent event) {
+            if (!pointerValid(mPivotEdge)) {
+                Rect bounds = new Rect();
+                mFullScreenMagnificationController.getMagnificationBounds(mDisplayId, bounds);
+                if (mOverscrollState == OVERSCROLL_LEFT_EDGE) {
+                    mPivotEdge.set(
+                            bounds.left, mFullScreenMagnificationController.getCenterY(mDisplayId));
+                } else if (mOverscrollState == OVERSCROLL_RIGHT_EDGE) {
+                    mPivotEdge.set(
+                            bounds.right,
+                            mFullScreenMagnificationController.getCenterY(mDisplayId));
+                }
                 mReachedEdgeCoord.set(event.getX(), event.getY());
                 mEdgeCooldown = true;
             }
         }
 
-        @Override
-        public void clear() {
+        private void onScrollStateChanged(MotionEvent first, MotionEvent second) {
+            if (mFullScreenMagnificationController.isAtEdge(mDisplayId)) {
+                vibrateIfNeeded(second);
+                setPivotEdge(second);
+            }
+            switch (mOverscrollState) {
+                case OVERSCROLL_NONE:
+                    onNoOverscroll(first, second);
+                    break;
+                case OVERSCROLL_VERTICAL_EDGE:
+                    onVerticalOverscroll();
+                    break;
+                case OVERSCROLL_LEFT_EDGE:
+                case OVERSCROLL_RIGHT_EDGE:
+                    onHorizontalOverscroll(second);
+                    break;
+                default:
+                    Slog.d(mLogTag, "Invalid overscroll state");
+                    break;
+            }
+        }
+
+        public void onNoOverscroll(MotionEvent first, MotionEvent second) {
+            mOverscrollState = overscrollState(second, new PointF(first.getX(), first.getY()));
+        }
+
+        public void onVerticalOverscroll() {
+            clearEdgeState();
+            transitionTo(mDelegatingState);
+        }
+
+        public void onHorizontalOverscroll(MotionEvent second) {
+            boolean reset = warpEffectReset(second);
+            if (reset) {
+                mFullScreenMagnificationController.reset(mDisplayId, /* animate */ true);
+                clearEdgeState();
+                transitionTo(mDelegatingState);
+            }
+        }
+
+        private void setScaleAndCenterToEdgeIfNeeded() {
+            if (mOverscrollState == OVERSCROLL_LEFT_EDGE
+                    || mOverscrollState == OVERSCROLL_RIGHT_EDGE) {
+                mFullScreenMagnificationController.setScaleAndCenter(
+                        mDisplayId,
+                        mFullScreenMagnificationController.getPersistedScale(mDisplayId),
+                        mPivotEdge.x,
+                        mPivotEdge.y,
+                        true,
+                        AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+            }
+        }
+
+        private void clearEdgeState() {
             mOverscrollState = OVERSCROLL_NONE;
             mPivotEdge.set(Float.NaN, Float.NaN);
             mReachedEdgeCoord.set(Float.NaN, Float.NaN);
             mEdgeCooldown = false;
         }
+
+        @Override
+        public String toString() {
+            return "OverscrollHandler {"
+                    + "mOverscrollState="
+                    + mOverscrollState
+                    + "mPivotEdge.x="
+                    + mPivotEdge.x
+                    + "mPivotEdge.y="
+                    + mPivotEdge.y
+                    + "mReachedEdgeCoord.x="
+                    + mReachedEdgeCoord.x
+                    + "mReachedEdgeCoord.y="
+                    + mReachedEdgeCoord.y
+                    + "mEdgeCooldown="
+                    + mEdgeCooldown
+                    + "}";
+        }
     }
 }
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionDefinitionsTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionDefinitionsTest.kt
new file mode 100644
index 0000000..832136c
--- /dev/null
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionDefinitionsTest.kt
@@ -0,0 +1,764 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.test
+
+import android.content.pm.PermissionInfo
+import android.os.Build
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.permission.Permission
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.pm.pkg.PackageState
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Parameterized test for testing permission definitions (adopt permissions, add permission groups,
+ * add permissions, trim permissions, trim permission states and revoke permissions on
+ * package update) for onStorageVolumeAdded() and onPackageAdded() in AppIdPermissionPolicy.
+ *
+ * Note that the evaluatePermissionState() call with permission changes
+ * (i.e. changedPermissionNames in AppIdPermissionPolicy) and the evaluatePermissionState() call
+ * with an installedPackageState is put in this test instead of
+ * AppIdPermissionPolicyPermissionStatesTest because these concepts don't apply to onUserAdded().
+ */
+@RunWith(Parameterized::class)
+class AppIdPermissionPolicyPermissionDefinitionsTest : BaseAppIdPermissionPolicyTest() {
+    @Parameterized.Parameter(0) lateinit var action: Action
+
+    @Test
+    fun testAdoptPermissions_permissionsOfMissingSystemApp_getsAdopted() {
+        testAdoptPermissions(hasMissingPackage = true, isSystem = true)
+
+        assertWithMessage(
+            "After $action is called for a null adopt permission package," +
+                " the permission package name: ${getPermission(PERMISSION_NAME_0)?.packageName}" +
+                " did not match the expected package name: $PACKAGE_NAME_0"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_0)
+    }
+
+    @Test
+    fun testAdoptPermissions_permissionsOfExistingSystemApp_notAdopted() {
+        testAdoptPermissions(isSystem = true)
+
+        assertWithMessage(
+            "After $action is called for a non-null adopt permission" +
+                " package, the permission package name:" +
+                " ${getPermission(PERMISSION_NAME_0)?.packageName} should not match the" +
+                " package name: $PACKAGE_NAME_0"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.packageName)
+            .isNotEqualTo(PACKAGE_NAME_0)
+    }
+
+    @Test
+    fun testAdoptPermissions_permissionsOfNonSystemApp_notAdopted() {
+        testAdoptPermissions(hasMissingPackage = true)
+
+        assertWithMessage(
+            "After $action is called for a non-system adopt permission" +
+                " package, the permission package name:" +
+                " ${getPermission(PERMISSION_NAME_0)?.packageName} should not match the" +
+                " package name: $PACKAGE_NAME_0"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.packageName)
+            .isNotEqualTo(PACKAGE_NAME_0)
+    }
+
+    private fun testAdoptPermissions(
+        hasMissingPackage: Boolean = false,
+        isSystem: Boolean = false
+    ) {
+        val parsedPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_1)
+        val packageToAdoptPermission = if (hasMissingPackage) {
+            mockPackageState(APP_ID_1, PACKAGE_NAME_1, isSystem = isSystem)
+        } else {
+            mockPackageState(
+                APP_ID_1,
+                mockAndroidPackage(
+                    PACKAGE_NAME_1,
+                    permissions = listOf(parsedPermission)
+                ),
+                isSystem = isSystem
+            )
+        }
+        addPackageState(packageToAdoptPermission)
+        addPermission(parsedPermission)
+
+        mutateState {
+            val installedPackage = mockPackageState(
+                APP_ID_0,
+                mockAndroidPackage(
+                    PACKAGE_NAME_0,
+                    permissions = listOf(defaultPermission),
+                    adoptPermissions = listOf(PACKAGE_NAME_1)
+                )
+            )
+            addPackageState(installedPackage, newState)
+            testAction(installedPackage)
+        }
+    }
+
+    @Test
+    fun testPermissionGroupDefinition_newPermissionGroup_getsDeclared() {
+        mutateState {
+            val packageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+            addPackageState(packageState, newState)
+            testAction(packageState)
+        }
+
+        assertWithMessage(
+            "After $action is called when there is no existing" +
+                " permission groups, the new permission group $PERMISSION_GROUP_NAME_0 is not added"
+        )
+            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.name)
+            .isEqualTo(PERMISSION_GROUP_NAME_0)
+    }
+
+    @Test
+    fun testPermissionGroupDefinition_systemAppTakingOverPermissionGroupDefinition_getsTakenOver() {
+        testTakingOverPermissionAndPermissionGroupDefinitions(newPermissionOwnerIsSystem = true)
+
+        assertWithMessage(
+            "After $action is called when $PERMISSION_GROUP_NAME_0 already" +
+                " exists in the system, the system app $PACKAGE_NAME_0 didn't takeover the" +
+                " ownership of this permission group"
+        )
+            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_0)
+    }
+
+    @Test
+    fun testPermissionGroupDefinition_instantApps_remainsUnchanged() {
+        testTakingOverPermissionAndPermissionGroupDefinitions(
+            newPermissionOwnerIsInstant = true,
+            permissionGroupAlreadyExists = false
+        )
+
+        assertWithMessage(
+            "After $action is called for an instant app," +
+                " the new permission group $PERMISSION_GROUP_NAME_0 should not be added"
+        )
+            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0))
+            .isNull()
+    }
+
+    @Test
+    fun testPermissionGroupDefinition_nonSystemAppTakingOverGroupDefinition_remainsUnchanged() {
+        testTakingOverPermissionAndPermissionGroupDefinitions()
+
+        assertWithMessage(
+            "After $action is called when $PERMISSION_GROUP_NAME_0 already" +
+                " exists in the system, non-system app $PACKAGE_NAME_0 shouldn't takeover" +
+                " ownership of this permission group"
+        )
+            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_1)
+    }
+
+    @Test
+    fun testPermissionGroupDefinition_takingOverGroupDeclaredBySystemApp_remainsUnchanged() {
+        testTakingOverPermissionAndPermissionGroupDefinitions(oldPermissionOwnerIsSystem = true)
+
+        assertWithMessage(
+            "After $action is called when $PERMISSION_GROUP_NAME_0 already" +
+                " exists in the system and is owned by a system app, app $PACKAGE_NAME_0" +
+                " shouldn't takeover ownership of this permission group"
+        )
+            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_1)
+    }
+
+    @Test
+    fun testPermissionDefinition_newPermission_getsDeclared() {
+        mutateState {
+            val packageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+            addPackageState(packageState, newState)
+            testAction(packageState)
+        }
+
+        assertWithMessage(
+            "After $action is called when there is no existing" +
+                " permissions, the new permission $PERMISSION_NAME_0 is not added"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.name)
+            .isEqualTo(PERMISSION_NAME_0)
+    }
+
+    @Test
+    fun testPermissionDefinition_configPermission_getsTakenOver() {
+        testTakingOverPermissionAndPermissionGroupDefinitions(
+            oldPermissionOwnerIsSystem = true,
+            newPermissionOwnerIsSystem = true,
+            type = Permission.TYPE_CONFIG,
+            isReconciled = false
+        )
+
+        assertWithMessage(
+            "After $action is called for a config permission with" +
+                " no owner, the ownership is not taken over by a system app $PACKAGE_NAME_0"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_0)
+    }
+
+    @Test
+    fun testPermissionDefinition_systemAppTakingOverPermissionDefinition_getsTakenOver() {
+        testTakingOverPermissionAndPermissionGroupDefinitions(newPermissionOwnerIsSystem = true)
+
+        assertWithMessage(
+            "After $action is called when $PERMISSION_NAME_0 already" +
+                " exists in the system, the system app $PACKAGE_NAME_0 didn't takeover ownership" +
+                " of this permission"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_0)
+    }
+
+    @Test
+    fun testPermissionDefinition_nonSystemAppTakingOverPermissionDefinition_remainsUnchanged() {
+        testTakingOverPermissionAndPermissionGroupDefinitions()
+
+        assertWithMessage(
+            "After $action is called when $PERMISSION_NAME_0 already" +
+                " exists in the system, the non-system app $PACKAGE_NAME_0 shouldn't takeover" +
+                " ownership of this permission"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_1)
+    }
+
+    @Test
+    fun testPermissionDefinition_takingOverPermissionDeclaredBySystemApp_remainsUnchanged() {
+        testTakingOverPermissionAndPermissionGroupDefinitions(oldPermissionOwnerIsSystem = true)
+
+        assertWithMessage(
+            "After $action is called when $PERMISSION_NAME_0 already" +
+                " exists in system and is owned by a system app, the $PACKAGE_NAME_0 shouldn't" +
+                " takeover ownership of this permission"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_1)
+    }
+
+    private fun testTakingOverPermissionAndPermissionGroupDefinitions(
+        oldPermissionOwnerIsSystem: Boolean = false,
+        newPermissionOwnerIsSystem: Boolean = false,
+        newPermissionOwnerIsInstant: Boolean = false,
+        permissionGroupAlreadyExists: Boolean = true,
+        permissionAlreadyExists: Boolean = true,
+        type: Int = Permission.TYPE_MANIFEST,
+        isReconciled: Boolean = true,
+    ) {
+        val oldPermissionOwnerPackageState = mockPackageState(
+            APP_ID_1,
+            PACKAGE_NAME_1,
+            isSystem = oldPermissionOwnerIsSystem
+        )
+        addPackageState(oldPermissionOwnerPackageState)
+        if (permissionGroupAlreadyExists) {
+            addPermissionGroup(mockParsedPermissionGroup(PERMISSION_GROUP_NAME_0, PACKAGE_NAME_1))
+        }
+        if (permissionAlreadyExists) {
+            addPermission(
+                mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_1),
+                type = type,
+                isReconciled = isReconciled
+            )
+        }
+
+        mutateState {
+            val newPermissionOwnerPackageState = mockPackageState(
+                APP_ID_0,
+                mockSimpleAndroidPackage(),
+                isSystem = newPermissionOwnerIsSystem,
+                isInstantApp = newPermissionOwnerIsInstant
+            )
+            addPackageState(newPermissionOwnerPackageState, newState)
+            testAction(newPermissionOwnerPackageState)
+        }
+    }
+
+    @Test
+    fun testPermissionChanged_permissionGroupChanged_getsRevoked() {
+        testPermissionChanged(
+            oldPermissionGroup = PERMISSION_GROUP_NAME_1,
+            newPermissionGroup = PERMISSION_GROUP_NAME_0
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package that has a permission group change" +
+                " for a permission it defines, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testPermissionChanged_protectionLevelChanged_getsRevoked() {
+        testPermissionChanged(newProtectionLevel = PermissionInfo.PROTECTION_INTERNAL)
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package that has a protection level change" +
+                " for a permission it defines, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    private fun testPermissionChanged(
+        oldPermissionGroup: String? = null,
+        newPermissionGroup: String? = null,
+        newProtectionLevel: Int = PermissionInfo.PROTECTION_DANGEROUS
+    ) {
+        val oldPermission = mockParsedPermission(
+            PERMISSION_NAME_0,
+            PACKAGE_NAME_0,
+            group = oldPermissionGroup,
+            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS
+        )
+        val oldPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(oldPermission))
+        )
+        addPackageState(oldPackageState)
+        addPermission(oldPermission)
+        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED)
+
+        mutateState {
+            val newPermission = mockParsedPermission(
+                PERMISSION_NAME_0,
+                PACKAGE_NAME_0,
+                group = newPermissionGroup,
+                protectionLevel = newProtectionLevel
+            )
+            val newPackageState = mockPackageState(
+                APP_ID_0,
+                mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(newPermission))
+            )
+            addPackageState(newPackageState, newState)
+            testAction(newPackageState)
+        }
+    }
+
+    @Test
+    fun testPermissionDeclaration_permissionTreeNoLongerDeclared_getsDefinitionRemoved() {
+        testPermissionDeclaration {}
+
+        assertWithMessage(
+            "After $action is called for a package that no longer defines a permission" +
+                " tree, the permission tree: $PERMISSION_NAME_0 in system state should be removed"
+        )
+            .that(getPermissionTree(PERMISSION_NAME_0))
+            .isNull()
+    }
+
+    @Test
+    fun testPermissionDeclaration_permissionTreeByDisabledSystemPackage_remainsUnchanged() {
+        testPermissionDeclaration {
+            val disabledSystemPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+            addDisabledSystemPackageState(disabledSystemPackageState)
+        }
+
+        assertWithMessage(
+            "After $action is called for a package that no longer defines" +
+                " a permission tree while this permission tree is still defined by" +
+                " a disabled system package, the permission tree: $PERMISSION_NAME_0 in" +
+                " system state should not be removed"
+        )
+            .that(getPermissionTree(PERMISSION_TREE_NAME))
+            .isNotNull()
+    }
+
+    @Test
+    fun testPermissionDeclaration_permissionNoLongerDeclared_getsDefinitionRemoved() {
+        testPermissionDeclaration {}
+
+        assertWithMessage(
+            "After $action is called for a package that no longer defines a permission," +
+                " the permission: $PERMISSION_NAME_0 in system state should be removed"
+        )
+            .that(getPermission(PERMISSION_NAME_0))
+            .isNull()
+    }
+
+    @Test
+    fun testPermissionDeclaration_permissionByDisabledSystemPackage_remainsUnchanged() {
+        testPermissionDeclaration {
+            val disabledSystemPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+            addDisabledSystemPackageState(disabledSystemPackageState)
+        }
+
+        assertWithMessage(
+            "After $action is called for a disabled system package and it's updated apk" +
+                " no longer defines a permission, the permission: $PERMISSION_NAME_0 in" +
+                " system state should not be removed"
+        )
+            .that(getPermission(PERMISSION_NAME_0))
+            .isNotNull()
+    }
+
+    private fun testPermissionDeclaration(additionalSetup: () -> Unit) {
+        val oldPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+        addPackageState(oldPackageState)
+        addPermission(defaultPermissionTree)
+        addPermission(defaultPermission)
+
+        additionalSetup()
+
+        mutateState {
+            val newPackageState = mockPackageState(APP_ID_0, mockAndroidPackage(PACKAGE_NAME_0))
+            addPackageState(newPackageState, newState)
+            testAction(newPackageState)
+        }
+    }
+
+    @Test
+    fun testTrimPermissionStates_permissionsNoLongerRequested_getsFlagsRevoked() {
+        val parsedPermission = mockParsedPermission(
+            PERMISSION_NAME_0,
+            PACKAGE_NAME_0,
+            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS
+        )
+        val oldPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(
+                PACKAGE_NAME_0,
+                permissions = listOf(parsedPermission),
+                requestedPermissions = setOf(PERMISSION_NAME_0)
+            )
+        )
+        addPackageState(oldPackageState)
+        addPermission(parsedPermission)
+        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED)
+
+        mutateState {
+            val newPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+            addPackageState(newPackageState, newState)
+            testAction(newPackageState)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package that no longer requests a permission" +
+                " the actual permission flags $actualFlags should match the" +
+                " expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testRevokePermissionsOnPackageUpdate_storageAndMediaDowngradingPastQ_getsRuntimeRevoked() {
+        testRevokePermissionsOnPackageUpdate(
+            PermissionFlags.RUNTIME_GRANTED,
+            newTargetSdkVersion = Build.VERSION_CODES.P
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package that's downgrading past Q" +
+                " the actual permission flags $actualFlags should match the" +
+                " expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testRevokePermissionsOnPackageUpdate_storageAndMediaNotDowngradingPastQ_remainsUnchanged() {
+        val oldFlags = PermissionFlags.RUNTIME_GRANTED
+        testRevokePermissionsOnPackageUpdate(
+            oldFlags,
+            oldTargetSdkVersion = Build.VERSION_CODES.P,
+            newTargetSdkVersion = Build.VERSION_CODES.P
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that's not downgrading past Q" +
+                " the actual permission flags $actualFlags should match the" +
+                " expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testRevokePermissionsOnPackageUpdate_policyFixedDowngradingPastQ_remainsUnchanged() {
+        val oldFlags = PermissionFlags.RUNTIME_GRANTED and PermissionFlags.POLICY_FIXED
+        testRevokePermissionsOnPackageUpdate(oldFlags, newTargetSdkVersion = Build.VERSION_CODES.P)
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that's downgrading past Q" +
+                " the actual permission flags with PermissionFlags.POLICY_FIXED $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testRevokePermissionsOnPackageUpdate_newlyRequestingLegacyExternalStorage_runtimeRevoked() {
+        testRevokePermissionsOnPackageUpdate(
+            PermissionFlags.RUNTIME_GRANTED,
+            oldTargetSdkVersion = Build.VERSION_CODES.P,
+            newTargetSdkVersion = Build.VERSION_CODES.P,
+            oldIsRequestLegacyExternalStorage = false
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package with" +
+                " newlyRequestingLegacyExternalStorage, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testRevokePermissionsOnPackageUpdate_missingOldPackage_remainsUnchanged() {
+        val oldFlags = PermissionFlags.RUNTIME_GRANTED
+        testRevokePermissionsOnPackageUpdate(
+            oldFlags,
+            newTargetSdkVersion = Build.VERSION_CODES.P,
+            isOldPackageMissing = true
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that's downgrading past Q" +
+                " and doesn't have the oldPackage, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    private fun testRevokePermissionsOnPackageUpdate(
+        oldFlags: Int,
+        oldTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+        newTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+        oldIsRequestLegacyExternalStorage: Boolean = true,
+        newIsRequestLegacyExternalStorage: Boolean = true,
+        isOldPackageMissing: Boolean = false
+    ) {
+        val parsedPermission = mockParsedPermission(
+            PERMISSION_READ_EXTERNAL_STORAGE,
+            PACKAGE_NAME_0,
+            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS
+        )
+        val oldPackageState = if (isOldPackageMissing) {
+            mockPackageState(APP_ID_0, PACKAGE_NAME_0)
+        } else {
+            mockPackageState(
+                APP_ID_0,
+                mockAndroidPackage(
+                    PACKAGE_NAME_0,
+                    targetSdkVersion = oldTargetSdkVersion,
+                    isRequestLegacyExternalStorage = oldIsRequestLegacyExternalStorage,
+                    requestedPermissions = setOf(PERMISSION_READ_EXTERNAL_STORAGE),
+                    permissions = listOf(parsedPermission)
+                )
+            )
+        }
+        addPackageState(oldPackageState)
+        addPermission(parsedPermission)
+        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE, oldFlags)
+
+        mutateState {
+            val newPackageState = mockPackageState(
+                APP_ID_0,
+                mockAndroidPackage(
+                    PACKAGE_NAME_0,
+                    targetSdkVersion = newTargetSdkVersion,
+                    isRequestLegacyExternalStorage = newIsRequestLegacyExternalStorage,
+                    requestedPermissions = setOf(PERMISSION_READ_EXTERNAL_STORAGE),
+                    permissions = listOf(parsedPermission)
+                )
+            )
+            addPackageState(newPackageState, newState)
+            testAction(newPackageState)
+        }
+    }
+
+    @Test
+    fun testEvaluatePermissionState_normalPermissionRequestedByInstalledPackage_getsGranted() {
+        val oldFlags = PermissionFlags.INSTALL_REVOKED
+        val permissionOwnerPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+        val installedPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+        )
+        addPackageState(permissionOwnerPackageState)
+        addPackageState(installedPackageState)
+        addPermission(defaultPermission)
+        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
+
+        mutateState {
+            testAction(installedPackageState)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a normal permission" +
+                " with the INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags since it's a new install"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    /**
+     * We set up a permission protection level change from SIGNATURE to NORMAL in order to make
+     * the permission a "changed permission" in order to test evaluatePermissionState() called by
+     * evaluatePermissionStateForAllPackages(). This makes the requestingPackageState not the
+     * installedPackageState so that we can test whether requesting by system package will give us
+     * the expected permission flags.
+     *
+     * Besides, this also helps us test evaluatePermissionStateForAllPackages(). Since both
+     * evaluatePermissionStateForAllPackages() and evaluateAllPermissionStatesForPackage() call
+     * evaluatePermissionState() in their implementations, we use these tests as the only tests
+     * that test evaluatePermissionStateForAllPackages()
+     */
+    @Test
+    fun testEvaluatePermissionState_normalPermissionRequestedBySystemPackage_getsGranted() {
+        testEvaluateNormalPermissionStateWithPermissionChanges(requestingPackageIsSystem = true)
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
+        assertWithMessage(
+            "After $action is called for a system package that requests a normal" +
+                " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_normalCompatibilityPermission_getsGranted() {
+        testEvaluateNormalPermissionStateWithPermissionChanges(
+            permissionName = PERMISSION_POST_NOTIFICATIONS,
+            requestingPackageTargetSdkVersion = Build.VERSION_CODES.S
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_POST_NOTIFICATIONS)
+        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a normal compatibility" +
+                " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_normalPermissionPreviouslyRevoked_getsInstallRevoked() {
+        testEvaluateNormalPermissionStateWithPermissionChanges()
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.INSTALL_REVOKED
+        assertWithMessage(
+            "After $action is called for a package that requests a normal" +
+                " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    private fun testEvaluateNormalPermissionStateWithPermissionChanges(
+        permissionName: String = PERMISSION_NAME_0,
+        requestingPackageTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+        requestingPackageIsSystem: Boolean = false
+    ) {
+        val oldParsedPermission = mockParsedPermission(
+            permissionName,
+            PACKAGE_NAME_0,
+            protectionLevel = PermissionInfo.PROTECTION_SIGNATURE
+        )
+        val oldPermissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(oldParsedPermission))
+        )
+        val requestingPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(
+                PACKAGE_NAME_1,
+                requestedPermissions = setOf(permissionName),
+                targetSdkVersion = requestingPackageTargetSdkVersion
+            ),
+            isSystem = requestingPackageIsSystem,
+        )
+        addPackageState(oldPermissionOwnerPackageState)
+        addPackageState(requestingPackageState)
+        addPermission(oldParsedPermission)
+        val oldFlags = PermissionFlags.INSTALL_REVOKED
+        setPermissionFlags(APP_ID_1, USER_ID_0, permissionName, oldFlags)
+
+        mutateState {
+            val newParsedPermission = mockParsedPermission(permissionName, PACKAGE_NAME_0)
+            val newPermissionOwnerPackageState = mockPackageState(
+                APP_ID_0,
+                mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(newParsedPermission))
+            )
+            addPackageState(newPermissionOwnerPackageState, newState)
+            testAction(newPermissionOwnerPackageState)
+        }
+    }
+
+    private fun MutateStateScope.testAction(packageState: PackageState) {
+        with(appIdPermissionPolicy) {
+            when (action) {
+                Action.ON_PACKAGE_ADDED -> onPackageAdded(packageState)
+                Action.ON_STORAGE_VOLUME_ADDED -> onStorageVolumeMounted(
+                    null,
+                    listOf(packageState.packageName),
+                    true
+                )
+            }
+        }
+    }
+
+    enum class Action { ON_PACKAGE_ADDED, ON_STORAGE_VOLUME_ADDED }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun data(): Array<Action> = Action.values()
+    }
+}
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
new file mode 100644
index 0000000..823ce45
--- /dev/null
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.test
+
+import android.content.pm.PermissionInfo
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.permission.PermissionFlags
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * A parameterized test for testing resetting runtime permissions for onPackageUninstalled()
+ * and resetRuntimePermissions() in AppIdPermissionPolicy
+ */
+@RunWith(Parameterized::class)
+class AppIdPermissionPolicyPermissionResetTest : BaseAppIdPermissionPolicyTest() {
+    @Parameterized.Parameter(0) lateinit var action: Action
+
+    @Test
+    fun testResetRuntimePermissions_runtimeGranted_getsRevoked() {
+        val oldFlags = PermissionFlags.RUNTIME_GRANTED
+        val expectedNewFlags = 0
+        testResetRuntimePermissions(oldFlags, expectedNewFlags)
+    }
+
+    @Test
+    fun testResetRuntimePermissions_roleGranted_getsGranted() {
+        val oldFlags = PermissionFlags.ROLE
+        val expectedNewFlags = PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED
+        testResetRuntimePermissions(oldFlags, expectedNewFlags)
+    }
+
+    @Test
+    fun testResetRuntimePermissions_nullAndroidPackage_remainsUnchanged() {
+        val oldFlags = PermissionFlags.RUNTIME_GRANTED
+        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED
+        testResetRuntimePermissions(oldFlags, expectedNewFlags, isAndroidPackageMissing = true)
+    }
+
+    private fun testResetRuntimePermissions(
+        oldFlags: Int,
+        expectedNewFlags: Int,
+        isAndroidPackageMissing: Boolean = false
+    ) {
+        val parsedPermission = mockParsedPermission(
+            PERMISSION_NAME_0,
+            PACKAGE_NAME_0,
+            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS,
+        )
+        val permissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
+        )
+        val requestingPackageState = if (isAndroidPackageMissing) {
+            mockPackageState(APP_ID_1, PACKAGE_NAME_1)
+        } else {
+            mockPackageState(
+                APP_ID_1,
+                mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+            )
+        }
+        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
+        addPackageState(permissionOwnerPackageState)
+        addPackageState(requestingPackageState)
+        addPermission(parsedPermission)
+
+        mutateState { testAction() }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        assertWithMessage(
+            "After resetting runtime permissions, permission flags did not match" +
+                " expected values: expectedNewFlags is $expectedNewFlags," +
+                " actualFlags is $actualFlags, while the oldFlags is $oldFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    private fun MutateStateScope.testAction(
+        packageName: String = PACKAGE_NAME_1,
+        appId: Int = APP_ID_1,
+        userId: Int = USER_ID_0
+    ) {
+        with(appIdPermissionPolicy) {
+            when (action) {
+                Action.ON_PACKAGE_UNINSTALLED -> onPackageUninstalled(packageName, appId, userId)
+                Action.RESET_RUNTIME_PERMISSIONS -> resetRuntimePermissions(packageName, userId)
+            }
+        }
+    }
+
+    enum class Action { ON_PACKAGE_UNINSTALLED, RESET_RUNTIME_PERMISSIONS }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun data(): Array<Action> = Action.values()
+    }
+}
\ No newline at end of file
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt
new file mode 100644
index 0000000..f085bd7
--- /dev/null
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt
@@ -0,0 +1,884 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.test
+
+import android.content.pm.PermissionInfo
+import android.os.Build
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.immutable.IndexedListSet
+import com.android.server.permission.access.immutable.MutableIndexedListSet
+import com.android.server.permission.access.immutable.MutableIndexedMap
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.pm.permission.PermissionAllowlist
+import com.android.server.pm.pkg.PackageState
+import com.android.server.testutils.mock
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * A parameterized test for testing evaluating permission states and inheriting implicit permission
+ * states for onUserAdded(), onStorageVolumeAdded() and onPackageAdded() in AppIdPermissionPolicy
+ */
+@RunWith(Parameterized::class)
+class AppIdPermissionPolicyPermissionStatesTest : BaseAppIdPermissionPolicyTest() {
+    @Parameterized.Parameter(0) lateinit var action: Action
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+        if (action == Action.ON_USER_ADDED) {
+            createUserState(USER_ID_NEW)
+        }
+    }
+
+    @Test
+    fun testEvaluatePermissionState_normalPermissionAlreadyGranted_remainsUnchanged() {
+        val oldFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.INSTALL_REVOKED
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_NORMAL) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests a normal permission" +
+                " with an existing INSTALL_GRANTED flag, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_normalPermissionNotInstallRevoked_getsGranted() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_NORMAL,
+            isNewInstall = true
+        ) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a normal permission" +
+                " with no existing flags, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_normalAppOpPermission_getsRoleAndUserSetFlagsPreserved() {
+        val oldFlags = PermissionFlags.ROLE or PermissionFlags.USER_SET
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_NORMAL or PermissionInfo.PROTECTION_FLAG_APPOP
+        ) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED or oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests a normal app op" +
+                " permission with existing ROLE and USER_SET flags, the actual permission flags" +
+                " $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_internalWasGrantedWithMissingPackage_getsProtectionGranted() {
+        val oldFlags = PermissionFlags.PROTECTION_GRANTED
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_INTERNAL) {
+            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
+            addPackageState(packageStateWithMissingPackage)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests an internal permission" +
+                " with missing android package and $oldFlags flag, the actual permission flags" +
+                " $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_internalAppOpPermission_getsRoleAndUserSetFlagsPreserved() {
+        val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.ROLE or
+            PermissionFlags.USER_SET
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_APPOP
+        ) {
+            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
+            addPackageState(packageStateWithMissingPackage)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests an internal permission" +
+                " with missing android package and $oldFlags flag and the permission isAppOp," +
+                " the actual permission flags $actualFlags should match the expected" +
+                " flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_internalDevelopmentPermission_getsRuntimeGrantedPreserved() {
+        val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_DEVELOPMENT
+        ) {
+            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
+            addPackageState(packageStateWithMissingPackage)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests an internal permission" +
+                " with missing android package and $oldFlags flag and permission isDevelopment," +
+                " the actual permission flags $actualFlags should match the expected" +
+                " flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_internalRolePermission_getsRoleAndRuntimeGrantedPreserved() {
+        val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.ROLE or
+            PermissionFlags.RUNTIME_GRANTED
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_ROLE
+        ) {
+            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
+            addPackageState(packageStateWithMissingPackage)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests an internal permission" +
+                " with missing android package and $oldFlags flag and the permission isRole," +
+                " the actual permission flags $actualFlags should match the expected" +
+                " flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_signaturePrivilegedPermissionNotAllowlisted_isNotGranted() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_SIGNATURE or PermissionInfo.PROTECTION_FLAG_PRIVILEGED,
+            isInstalledPackageSystem = true,
+            isInstalledPackagePrivileged = true,
+            isInstalledPackageProduct = true,
+            // To mock the return value of shouldGrantPrivilegedOrOemPermission()
+            isInstalledPackageVendor = true,
+            isNewInstall = true
+        ) {
+            val platformPackage = mockPackageState(
+                PLATFORM_APP_ID,
+                mockAndroidPackage(PLATFORM_PACKAGE_NAME)
+            )
+            setupAllowlist(PACKAGE_NAME_1, false)
+            addPackageState(platformPackage)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests a signature privileged" +
+                " permission that's not allowlisted, the actual permission" +
+                " flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_nonPrivilegedShouldGrantBySignature_getsProtectionGranted() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_SIGNATURE,
+            isInstalledPackageSystem = true,
+            isInstalledPackagePrivileged = true,
+            isInstalledPackageProduct = true,
+            isInstalledPackageSignatureMatching = true,
+            isInstalledPackageVendor = true,
+            isNewInstall = true
+        ) {
+            val platformPackage = mockPackageState(
+                PLATFORM_APP_ID,
+                mockAndroidPackage(PLATFORM_PACKAGE_NAME, isSignatureMatching = true)
+            )
+            setupAllowlist(PACKAGE_NAME_1, false)
+            addPackageState(platformPackage)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.PROTECTION_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a signature" +
+                " non-privileged permission, the actual permission" +
+                " flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_privilegedAllowlistShouldGrantByProtectionFlags_getsGranted() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_SIGNATURE or PermissionInfo.PROTECTION_FLAG_PRIVILEGED,
+            isInstalledPackageSystem = true,
+            isInstalledPackagePrivileged = true,
+            isInstalledPackageProduct = true,
+            isNewInstall = true
+        ) {
+            val platformPackage = mockPackageState(
+                PLATFORM_APP_ID,
+                mockAndroidPackage(PLATFORM_PACKAGE_NAME)
+            )
+            setupAllowlist(PACKAGE_NAME_1, true)
+            addPackageState(platformPackage)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.PROTECTION_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a signature privileged" +
+                " permission that's allowlisted and should grant by protection flags, the actual" +
+                " permission flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    private fun setupAllowlist(
+        packageName: String,
+        allowlistState: Boolean,
+        state: MutableAccessState = oldState
+    ) {
+        state.mutateExternalState().setPrivilegedPermissionAllowlistPackages(
+            MutableIndexedListSet<String>().apply { add(packageName) }
+        )
+        val mockAllowlist = mock<PermissionAllowlist> {
+            whenever(
+                getProductPrivilegedAppAllowlistState(packageName, PERMISSION_NAME_0)
+            ).thenReturn(allowlistState)
+        }
+        state.mutateExternalState().setPermissionAllowlist(mockAllowlist)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_nonRuntimeFlagsOnRuntimePermissions_getsCleared() {
+        val oldFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.PREGRANT or
+            PermissionFlags.RUNTIME_GRANTED
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.PREGRANT or PermissionFlags.RUNTIME_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " with existing $oldFlags flags, the actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_newPermissionsForPreM_requiresUserReview() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_DANGEROUS,
+            installedPackageTargetSdkVersion = Build.VERSION_CODES.LOLLIPOP,
+            isNewInstall = true
+        ) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " with no existing flags in pre M, actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_legacyOrImplicitGrantedPreviouslyRevoked_getsAppOpRevoked() {
+        val oldFlags = PermissionFlags.USER_FIXED
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_DANGEROUS,
+            installedPackageTargetSdkVersion = Build.VERSION_CODES.LOLLIPOP
+        ) {
+            setPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0, oldFlags)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.USER_FIXED or
+            PermissionFlags.APP_OP_REVOKED
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " that should be LEGACY_GRANTED or IMPLICIT_GRANTED that was previously revoked," +
+                " the actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_legacyGrantedForPostM_userReviewRequirementRemoved() {
+        val oldFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " that used to require user review, the user review requirement should be removed" +
+                " if it's upgraded to post M. The actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_legacyGrantedPermissionsAlreadyReviewedForPostM_getsGranted() {
+        val oldFlags = PermissionFlags.LEGACY_GRANTED
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " that was already reviewed by the user, the permission should be RUNTIME_GRANTED" +
+                " if it's upgraded to post M. The actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_leanbackNotificationPermissionsForPostM_getsImplicitGranted() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_DANGEROUS,
+            permissionName = PERMISSION_POST_NOTIFICATIONS,
+            isNewInstall = true
+        ) {
+            oldState.mutateExternalState().setLeanback(true)
+        }
+
+        val actualFlags = getPermissionFlags(
+            APP_ID_1,
+            getUserIdEvaluated(),
+            PERMISSION_POST_NOTIFICATIONS
+        )
+        val expectedNewFlags = PermissionFlags.IMPLICIT_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime notification" +
+                " permission when isLeanback, the actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_implicitSourceFromNonRuntime_getsImplicitGranted() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_DANGEROUS,
+            implicitPermissions = setOf(PERMISSION_NAME_0),
+            isNewInstall = true
+        ) {
+            oldState.mutateExternalState().setImplicitToSourcePermissions(
+                MutableIndexedMap<String, IndexedListSet<String>>().apply {
+                    put(PERMISSION_NAME_0, MutableIndexedListSet<String>().apply {
+                        add(PERMISSION_NAME_1)
+                    })
+                }
+            )
+            addPermission(mockParsedPermission(PERMISSION_NAME_1, PACKAGE_NAME_0))
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime implicit" +
+                " permission that's source from a non-runtime permission, the actual permission" +
+                " flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    /**
+     * For a legacy granted or implicit permission during the app upgrade, when the permission
+     * should no longer be legacy or implicit granted, we want to remove the APP_OP_REVOKED flag
+     * so that the app can request the permission.
+     */
+    @Test
+    fun testEvaluatePermissionState_noLongerLegacyOrImplicitGranted_canBeRequested() {
+        val oldFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.APP_OP_REVOKED or
+            PermissionFlags.RUNTIME_GRANTED
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " that is no longer LEGACY_GRANTED or IMPLICIT_GRANTED, the actual permission" +
+                " flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_noLongerImplicit_getsRuntimeAndImplicitFlagsRemoved() {
+        val oldFlags = PermissionFlags.IMPLICIT or PermissionFlags.RUNTIME_GRANTED or
+            PermissionFlags.USER_SET or PermissionFlags.USER_FIXED
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " that is no longer implicit and we shouldn't retain as nearby device" +
+                " permissions, the actual permission flags $actualFlags should match the expected" +
+                " flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_noLongerImplicitNearbyWasGranted_getsRuntimeGranted() {
+        val oldFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_DANGEROUS,
+            permissionName = PERMISSION_BLUETOOTH_CONNECT,
+            requestedPermissions = setOf(
+                PERMISSION_BLUETOOTH_CONNECT,
+                PERMISSION_ACCESS_BACKGROUND_LOCATION
+            )
+        ) {
+            setPermissionFlags(
+                APP_ID_1,
+                getUserIdEvaluated(),
+                PERMISSION_ACCESS_BACKGROUND_LOCATION,
+                PermissionFlags.RUNTIME_GRANTED
+            )
+        }
+
+        val actualFlags = getPermissionFlags(
+            APP_ID_1,
+            getUserIdEvaluated(),
+            PERMISSION_BLUETOOTH_CONNECT
+        )
+        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime nearby device" +
+                " permission that was granted by implicit, the actual permission flags" +
+                " $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_noLongerImplicitSystemOrPolicyFixedWasGranted_runtimeGranted() {
+        val oldFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT or
+            PermissionFlags.SYSTEM_FIXED
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.SYSTEM_FIXED
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " that was granted and is no longer implicit and is SYSTEM_FIXED or POLICY_FIXED," +
+                " the actual permission flags $actualFlags should match the expected" +
+                " flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_restrictedPermissionsNotExempt_getsRestrictionFlags() {
+        val oldFlags = PermissionFlags.RESTRICTION_REVOKED
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_DANGEROUS,
+            permissionInfoFlags = PermissionInfo.FLAG_HARD_RESTRICTED
+        ) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime hard" +
+                " restricted permission that is not exempted, the actual permission flags" +
+                " $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_restrictedPermissionsIsExempted_clearsRestrictionFlags() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_DANGEROUS,
+            permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED
+        ) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.UPGRADE_EXEMPT
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime soft" +
+                " restricted permission that is exempted, the actual permission flags" +
+                " $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testInheritImplicitPermissionStates_runtimeExistingImplicit_sourceFlagsNotInherited() {
+        val oldImplicitPermissionFlags = PermissionFlags.USER_FIXED
+        testInheritImplicitPermissionStates(
+            implicitPermissionFlags = oldImplicitPermissionFlags,
+            isNewInstallAndNewPermission = false
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldImplicitPermissionFlags or PermissionFlags.IMPLICIT_GRANTED or
+            PermissionFlags.APP_OP_REVOKED
+        assertWithMessage(
+            "After $action is called for a package that requests a permission that is" +
+                " implicit, existing and runtime, it should not inherit the runtime flags from" +
+                " the source permission. Hence the actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testInheritImplicitPermissionStates_nonRuntimeNewImplicit_sourceFlagsNotInherited() {
+        testInheritImplicitPermissionStates(
+            implicitPermissionProtectionLevel = PermissionInfo.PROTECTION_NORMAL
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a permission that is" +
+                " implicit, new and non-runtime, it should not inherit the runtime flags from" +
+                " the source permission. Hence the actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testInheritImplicitPermissionStates_runtimeNewImplicitPermissions_sourceFlagsInherited() {
+        val sourceRuntimeFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET
+        testInheritImplicitPermissionStates(sourceRuntimeFlags = sourceRuntimeFlags)
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = sourceRuntimeFlags or PermissionFlags.IMPLICIT_GRANTED or
+            PermissionFlags.IMPLICIT
+        assertWithMessage(
+            "After $action is called for a package that requests a permission that is" +
+                " implicit, new and runtime, it should inherit the runtime flags from" +
+                " the source permission. Hence the actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testInheritImplicitPermissionStates_grantingNewFromRevokeImplicit_onlyInheritFromSource() {
+        val sourceRuntimeFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET
+        testInheritImplicitPermissionStates(
+            implicitPermissionFlags = PermissionFlags.POLICY_FIXED,
+            sourceRuntimeFlags = sourceRuntimeFlags,
+            isAnySourcePermissionNonRuntime = false
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = sourceRuntimeFlags or PermissionFlags.IMPLICIT
+        assertWithMessage(
+            "After $action is called for a package that requests a permission that is" +
+                " implicit, existing, runtime and revoked, it should only inherit runtime flags" +
+                " from source permission. Hence the actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    /**
+     * If it's a media implicit permission (one of RETAIN_IMPLICIT_FLAGS_PERMISSIONS), we want to
+     * remove the IMPLICIT flag so that they will be granted when they are no longer implicit.
+     * (instead of revoking it)
+     */
+    @Test
+    fun testInheritImplicitPermissionStates_mediaImplicitPermissions_getsImplicitFlagRemoved() {
+        val sourceRuntimeFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET
+        testInheritImplicitPermissionStates(
+            implicitPermissionName = PERMISSION_ACCESS_MEDIA_LOCATION,
+            sourceRuntimeFlags = sourceRuntimeFlags
+        )
+
+        val actualFlags = getPermissionFlags(
+            APP_ID_1,
+            getUserIdEvaluated(),
+            PERMISSION_ACCESS_MEDIA_LOCATION
+        )
+        val expectedNewFlags = sourceRuntimeFlags or PermissionFlags.IMPLICIT_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a media permission that" +
+                " is implicit, new and runtime, it should inherit the runtime flags from" +
+                " the source permission and have the IMPLICIT flag removed. Hence the actual" +
+                " permission flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    private fun testInheritImplicitPermissionStates(
+        implicitPermissionName: String = PERMISSION_NAME_0,
+        implicitPermissionFlags: Int = 0,
+        implicitPermissionProtectionLevel: Int = PermissionInfo.PROTECTION_DANGEROUS,
+        sourceRuntimeFlags: Int = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET,
+        isAnySourcePermissionNonRuntime: Boolean = true,
+        isNewInstallAndNewPermission: Boolean = true
+    ) {
+        val userId = getUserIdEvaluated()
+        val implicitPermission = mockParsedPermission(
+            implicitPermissionName,
+            PACKAGE_NAME_0,
+            protectionLevel = implicitPermissionProtectionLevel,
+        )
+        // For source from non-runtime in order to grant by implicit
+        val sourcePermission1 = mockParsedPermission(
+            PERMISSION_NAME_1,
+            PACKAGE_NAME_0,
+            protectionLevel = if (isAnySourcePermissionNonRuntime) {
+                PermissionInfo.PROTECTION_NORMAL
+            } else {
+                PermissionInfo.PROTECTION_DANGEROUS
+            }
+        )
+        // For inheriting runtime flags
+        val sourcePermission2 = mockParsedPermission(
+            PERMISSION_NAME_2,
+            PACKAGE_NAME_0,
+            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS,
+        )
+        val permissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(
+                PACKAGE_NAME_0,
+                permissions = listOf(implicitPermission, sourcePermission1, sourcePermission2)
+            )
+        )
+        val installedPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(
+                PACKAGE_NAME_1,
+                requestedPermissions = setOf(
+                    implicitPermissionName,
+                    PERMISSION_NAME_1,
+                    PERMISSION_NAME_2
+                ),
+                implicitPermissions = setOf(implicitPermissionName)
+            )
+        )
+        oldState.mutateExternalState().setImplicitToSourcePermissions(
+            MutableIndexedMap<String, IndexedListSet<String>>().apply {
+                put(implicitPermissionName, MutableIndexedListSet<String>().apply {
+                    add(PERMISSION_NAME_1)
+                    add(PERMISSION_NAME_2)
+                })
+            }
+        )
+        addPackageState(permissionOwnerPackageState)
+        addPermission(implicitPermission)
+        addPermission(sourcePermission1)
+        addPermission(sourcePermission2)
+        if (!isNewInstallAndNewPermission) {
+            addPackageState(installedPackageState)
+            setPermissionFlags(APP_ID_1, userId, implicitPermissionName, implicitPermissionFlags)
+        }
+        setPermissionFlags(APP_ID_1, userId, PERMISSION_NAME_2, sourceRuntimeFlags)
+
+        mutateState {
+            if (isNewInstallAndNewPermission) {
+                addPackageState(installedPackageState)
+                setPermissionFlags(
+                    APP_ID_1,
+                    userId,
+                    implicitPermissionName,
+                    implicitPermissionFlags,
+                    newState
+                )
+            }
+            testAction(installedPackageState)
+        }
+    }
+
+    /**
+     * Setup simple package states for testing evaluatePermissionState().
+     * permissionOwnerPackageState is definer of permissionName with APP_ID_0.
+     * installedPackageState is the installed package that requests permissionName with APP_ID_1.
+     *
+     * @param oldFlags the existing permission flags for APP_ID_1, userId, permissionName
+     * @param protectionLevel the protectionLevel for the permission
+     * @param permissionName the name of the permission (1) being defined (2) of the oldFlags, and
+     *                       (3) requested by installedPackageState
+     * @param requestedPermissions the permissions requested by installedPackageState
+     * @param implicitPermissions the implicit permissions of installedPackageState
+     * @param permissionInfoFlags the flags for the permission itself
+     * @param isInstalledPackageSystem whether installedPackageState is a system package
+     *
+     * @return installedPackageState
+     */
+    private fun testEvaluatePermissionState(
+        oldFlags: Int,
+        protectionLevel: Int,
+        permissionName: String = PERMISSION_NAME_0,
+        requestedPermissions: Set<String> = setOf(permissionName),
+        implicitPermissions: Set<String> = emptySet(),
+        permissionInfoFlags: Int = 0,
+        isInstalledPackageSystem: Boolean = false,
+        isInstalledPackagePrivileged: Boolean = false,
+        isInstalledPackageProduct: Boolean = false,
+        isInstalledPackageSignatureMatching: Boolean = false,
+        isInstalledPackageVendor: Boolean = false,
+        installedPackageTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+        isNewInstall: Boolean = false,
+        additionalSetup: () -> Unit
+    ) {
+        val userId = getUserIdEvaluated()
+        val parsedPermission = mockParsedPermission(
+            permissionName,
+            PACKAGE_NAME_0,
+            protectionLevel = protectionLevel,
+            flags = permissionInfoFlags
+        )
+        val permissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
+        )
+        val installedPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(
+                PACKAGE_NAME_1,
+                requestedPermissions = requestedPermissions,
+                implicitPermissions = implicitPermissions,
+                targetSdkVersion = installedPackageTargetSdkVersion,
+                isSignatureMatching = isInstalledPackageSignatureMatching
+            ),
+            isSystem = isInstalledPackageSystem,
+            isPrivileged = isInstalledPackagePrivileged,
+            isProduct = isInstalledPackageProduct,
+            isVendor = isInstalledPackageVendor
+        )
+        addPackageState(permissionOwnerPackageState)
+        if (!isNewInstall) {
+            addPackageState(installedPackageState)
+            setPermissionFlags(APP_ID_1, userId, permissionName, oldFlags)
+        }
+        addPermission(parsedPermission)
+
+        additionalSetup()
+
+        mutateState {
+            if (isNewInstall) {
+                addPackageState(installedPackageState, newState)
+                setPermissionFlags(APP_ID_1, userId, permissionName, oldFlags, newState)
+            }
+            testAction(installedPackageState)
+        }
+    }
+
+    private fun getUserIdEvaluated(): Int = when (action) {
+        Action.ON_USER_ADDED -> USER_ID_NEW
+        Action.ON_STORAGE_VOLUME_ADDED, Action.ON_PACKAGE_ADDED -> USER_ID_0
+    }
+
+    private fun MutateStateScope.testAction(packageState: PackageState) {
+        with(appIdPermissionPolicy) {
+            when (action) {
+                Action.ON_USER_ADDED -> onUserAdded(getUserIdEvaluated())
+                Action.ON_STORAGE_VOLUME_ADDED -> onStorageVolumeMounted(
+                    null,
+                    listOf(packageState.packageName),
+                    true
+                )
+                Action.ON_PACKAGE_ADDED -> onPackageAdded(packageState)
+            }
+        }
+    }
+
+    enum class Action { ON_USER_ADDED, ON_STORAGE_VOLUME_ADDED, ON_PACKAGE_ADDED }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun data(): Array<Action> = Action.values()
+    }
+}
\ No newline at end of file
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
deleted file mode 100644
index 3cf57a3..0000000
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
+++ /dev/null
@@ -1,1937 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.permission.test
-
-import android.Manifest
-import android.content.pm.PackageManager
-import android.content.pm.PermissionGroupInfo
-import android.content.pm.PermissionInfo
-import android.content.pm.SigningDetails
-import android.os.Build
-import android.os.Bundle
-import android.util.ArrayMap
-import android.util.SparseArray
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.modules.utils.testing.ExtendedMockitoRule
-import com.android.server.extendedtestutils.wheneverStatic
-import com.android.server.permission.access.MutableAccessState
-import com.android.server.permission.access.MutableUserState
-import com.android.server.permission.access.MutateStateScope
-import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
-import com.android.server.permission.access.permission.AppIdPermissionPolicy
-import com.android.server.permission.access.permission.Permission
-import com.android.server.permission.access.permission.PermissionFlags
-import com.android.server.permission.access.util.hasBits
-import com.android.server.pm.parsing.PackageInfoUtils
-import com.android.server.pm.permission.PermissionAllowlist
-import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.PackageState
-import com.android.server.pm.pkg.PackageUserState
-import com.android.server.pm.pkg.component.ParsedPermission
-import com.android.server.pm.pkg.component.ParsedPermissionGroup
-import com.android.server.testutils.any
-import com.android.server.testutils.mock
-import com.android.server.testutils.whenever
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyLong
-
-/**
- * Mocking unit test for AppIdPermissionPolicy.
- */
-@RunWith(AndroidJUnit4::class)
-class AppIdPermissionPolicyTest {
-    private lateinit var oldState: MutableAccessState
-    private lateinit var newState: MutableAccessState
-
-    private val defaultPermissionGroup = mockParsedPermissionGroup(
-        PERMISSION_GROUP_NAME_0,
-        PACKAGE_NAME_0
-    )
-    private val defaultPermissionTree = mockParsedPermission(
-        PERMISSION_TREE_NAME,
-        PACKAGE_NAME_0,
-        isTree = true
-    )
-    private val defaultPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0)
-
-    private val appIdPermissionPolicy = AppIdPermissionPolicy()
-
-    @Rule
-    @JvmField
-    val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
-        .spyStatic(PackageInfoUtils::class.java)
-        .build()
-
-    @Before
-    fun setUp() {
-        oldState = MutableAccessState()
-        createUserState(USER_ID_0)
-        oldState.mutateExternalState().setPackageStates(ArrayMap())
-        oldState.mutateExternalState().setDisabledSystemPackageStates(ArrayMap())
-        mockPackageInfoUtilsGeneratePermissionInfo()
-        mockPackageInfoUtilsGeneratePermissionGroupInfo()
-    }
-
-    private fun createUserState(userId: Int) {
-        oldState.mutateExternalState().mutateUserIds().add(userId)
-        oldState.mutateUserStatesNoWrite().put(userId, MutableUserState())
-    }
-
-    private fun mockPackageInfoUtilsGeneratePermissionInfo() {
-        wheneverStatic {
-            PackageInfoUtils.generatePermissionInfo(any(ParsedPermission::class.java), anyLong())
-        }.thenAnswer { invocation ->
-            val parsedPermission = invocation.getArgument<ParsedPermission>(0)
-            val generateFlags = invocation.getArgument<Long>(1)
-            PermissionInfo(parsedPermission.backgroundPermission).apply {
-                name = parsedPermission.name
-                packageName = parsedPermission.packageName
-                metaData = if (generateFlags.toInt().hasBits(PackageManager.GET_META_DATA)) {
-                    parsedPermission.metaData
-                } else {
-                    null
-                }
-                @Suppress("DEPRECATION")
-                protectionLevel = parsedPermission.protectionLevel
-                group = parsedPermission.group
-                flags = parsedPermission.flags
-            }
-        }
-    }
-
-    private fun mockPackageInfoUtilsGeneratePermissionGroupInfo() {
-        wheneverStatic {
-            PackageInfoUtils.generatePermissionGroupInfo(
-                any(ParsedPermissionGroup::class.java),
-                anyLong()
-            )
-        }.thenAnswer { invocation ->
-            val parsedPermissionGroup = invocation.getArgument<ParsedPermissionGroup>(0)
-            val generateFlags = invocation.getArgument<Long>(1)
-            @Suppress("DEPRECATION")
-            PermissionGroupInfo().apply {
-                name = parsedPermissionGroup.name
-                packageName = parsedPermissionGroup.packageName
-                metaData = if (generateFlags.toInt().hasBits(PackageManager.GET_META_DATA)) {
-                    parsedPermissionGroup.metaData
-                } else {
-                    null
-                }
-                flags = parsedPermissionGroup.flags
-            }
-        }
-    }
-
-    @Test
-    fun testResetRuntimePermissions_runtimeGranted_getsRevoked() {
-        val oldFlags = PermissionFlags.RUNTIME_GRANTED
-        val expectedNewFlags = 0
-        testResetRuntimePermissions(oldFlags, expectedNewFlags)
-    }
-
-    @Test
-    fun testResetRuntimePermissions_roleGranted_getsGranted() {
-        val oldFlags = PermissionFlags.ROLE
-        val expectedNewFlags = PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED
-        testResetRuntimePermissions(oldFlags, expectedNewFlags)
-    }
-
-    @Test
-    fun testResetRuntimePermissions_nullAndroidPackage_remainsUnchanged() {
-        val oldFlags = PermissionFlags.RUNTIME_GRANTED
-        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED
-        testResetRuntimePermissions(oldFlags, expectedNewFlags, isAndroidPackageMissing = true)
-    }
-
-    private fun testResetRuntimePermissions(
-        oldFlags: Int,
-        expectedNewFlags: Int,
-        isAndroidPackageMissing: Boolean = false
-    ) {
-        val parsedPermission = mockParsedPermission(
-            PERMISSION_NAME_0,
-            PACKAGE_NAME_0,
-            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS,
-        )
-        val permissionOwnerPackageState = mockPackageState(
-            APP_ID_0,
-            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
-        )
-        val requestingPackageState = if (isAndroidPackageMissing) {
-            mockPackageState(APP_ID_1, PACKAGE_NAME_1)
-        } else {
-            mockPackageState(
-                APP_ID_1,
-                mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
-            )
-        }
-        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
-        addPackageState(permissionOwnerPackageState)
-        addPackageState(requestingPackageState)
-        addPermission(parsedPermission)
-
-        mutateState {
-            with(appIdPermissionPolicy) {
-                resetRuntimePermissions(PACKAGE_NAME_1, USER_ID_0)
-            }
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        assertWithMessage(
-            "After resetting runtime permissions, permission flags did not match" +
-                " expected values: expectedNewFlags is $expectedNewFlags," +
-                " actualFlags is $actualFlags, while the oldFlags is $oldFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionsOfMissingSystemApp_getsAdopted() {
-        testAdoptPermissions(hasMissingPackage = true, isSystem = true)
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a null adopt permission package," +
-                " the permission package name: ${getPermission(PERMISSION_NAME_0)?.packageName}" +
-                " did not match the expected package name: $PACKAGE_NAME_0"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_0)
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionsOfExistingSystemApp_notAdopted() {
-        testAdoptPermissions(isSystem = true)
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a non-null adopt permission" +
-                " package, the permission package name:" +
-                " ${getPermission(PERMISSION_NAME_0)?.packageName} should not match the" +
-                " package name: $PACKAGE_NAME_0"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.packageName)
-            .isNotEqualTo(PACKAGE_NAME_0)
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionsOfNonSystemApp_notAdopted() {
-        testAdoptPermissions(hasMissingPackage = true)
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a non-system adopt permission" +
-                " package, the permission package name:" +
-                " ${getPermission(PERMISSION_NAME_0)?.packageName} should not match the" +
-                " package name: $PACKAGE_NAME_0"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.packageName)
-            .isNotEqualTo(PACKAGE_NAME_0)
-    }
-
-    private fun testAdoptPermissions(
-        hasMissingPackage: Boolean = false,
-        isSystem: Boolean = false
-    ) {
-        val parsedPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_1)
-        val packageToAdoptPermission = if (hasMissingPackage) {
-            mockPackageState(APP_ID_1, PACKAGE_NAME_1, isSystem = isSystem)
-        } else {
-            mockPackageState(
-                APP_ID_1,
-                mockAndroidPackage(
-                    PACKAGE_NAME_1,
-                    permissions = listOf(parsedPermission)
-                ),
-                isSystem = isSystem
-            )
-        }
-        addPackageState(packageToAdoptPermission)
-        addPermission(parsedPermission)
-
-        mutateState {
-            val installedPackage = mockPackageState(
-                APP_ID_0,
-                mockAndroidPackage(
-                    PACKAGE_NAME_0,
-                    permissions = listOf(defaultPermission),
-                    adoptPermissions = listOf(PACKAGE_NAME_1)
-                )
-            )
-            addPackageState(installedPackage, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(installedPackage)
-            }
-        }
-    }
-
-    @Test
-    fun testOnPackageAdded_newPermissionGroup_getsDeclared() {
-        mutateState {
-            val packageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
-            addPackageState(packageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(packageState)
-            }
-        }
-
-        assertWithMessage(
-            "After onPackageAdded() is called when there is no existing" +
-                " permission groups, the new permission group $PERMISSION_GROUP_NAME_0 is not added"
-        )
-            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.name)
-            .isEqualTo(PERMISSION_GROUP_NAME_0)
-    }
-
-    @Test
-    fun testOnPackageAdded_systemAppTakingOverPermissionGroupDefinition_getsTakenOver() {
-        testTakingOverPermissionAndPermissionGroupDefinitions(newPermissionOwnerIsSystem = true)
-
-        assertWithMessage(
-            "After onPackageAdded() is called when $PERMISSION_GROUP_NAME_0 already" +
-                " exists in the system, the system app $PACKAGE_NAME_0 didn't takeover the" +
-                " ownership of this permission group"
-        )
-            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_0)
-    }
-
-    @Test
-    fun testOnPackageAdded_instantApps_remainsUnchanged() {
-        testTakingOverPermissionAndPermissionGroupDefinitions(
-            newPermissionOwnerIsInstant = true,
-            permissionGroupAlreadyExists = false
-        )
-
-        assertWithMessage(
-            "After onPackageAdded() is called for an instant app," +
-                " the new permission group $PERMISSION_GROUP_NAME_0 should not be added"
-        )
-            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0))
-            .isNull()
-    }
-
-    @Test
-    fun testOnPackageAdded_nonSystemAppTakingOverPermissionGroupDefinition_remainsUnchanged() {
-        testTakingOverPermissionAndPermissionGroupDefinitions()
-
-        assertWithMessage(
-            "After onPackageAdded() is called when $PERMISSION_GROUP_NAME_0 already" +
-                " exists in the system, non-system app $PACKAGE_NAME_0 shouldn't takeover" +
-                " ownership of this permission group"
-        )
-            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_1)
-    }
-
-    @Test
-    fun testOnPackageAdded_takingOverPermissionGroupDeclaredBySystemApp_remainsUnchanged() {
-        testTakingOverPermissionAndPermissionGroupDefinitions(oldPermissionOwnerIsSystem = true)
-
-        assertWithMessage(
-            "After onPackageAdded() is called when $PERMISSION_GROUP_NAME_0 already" +
-                " exists in the system and is owned by a system app, app $PACKAGE_NAME_0" +
-                " shouldn't takeover ownership of this permission group"
-        )
-            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_1)
-    }
-
-    @Test
-    fun testOnPackageAdded_newPermission_getsDeclared() {
-        mutateState {
-            val packageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
-            addPackageState(packageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(packageState)
-            }
-        }
-
-        assertWithMessage(
-            "After onPackageAdded() is called when there is no existing" +
-                " permissions, the new permission $PERMISSION_NAME_0 is not added"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.name)
-            .isEqualTo(PERMISSION_NAME_0)
-    }
-
-    @Test
-    fun testOnPackageAdded_configPermission_getsTakenOver() {
-        testTakingOverPermissionAndPermissionGroupDefinitions(
-            oldPermissionOwnerIsSystem = true,
-            newPermissionOwnerIsSystem = true,
-            type = Permission.TYPE_CONFIG,
-            isReconciled = false
-        )
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a config permission with" +
-                " no owner, the ownership is not taken over by a system app $PACKAGE_NAME_0"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_0)
-    }
-
-    @Test
-    fun testOnPackageAdded_systemAppTakingOverPermissionDefinition_getsTakenOver() {
-        testTakingOverPermissionAndPermissionGroupDefinitions(newPermissionOwnerIsSystem = true)
-
-        assertWithMessage(
-            "After onPackageAdded() is called when $PERMISSION_NAME_0 already" +
-                " exists in the system, the system app $PACKAGE_NAME_0 didn't takeover ownership" +
-                " of this permission"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_0)
-    }
-
-    @Test
-    fun testOnPackageAdded_nonSystemAppTakingOverPermissionDefinition_remainsUnchanged() {
-        testTakingOverPermissionAndPermissionGroupDefinitions()
-
-        assertWithMessage(
-            "After onPackageAdded() is called when $PERMISSION_NAME_0 already" +
-                " exists in the system, the non-system app $PACKAGE_NAME_0 shouldn't takeover" +
-                " ownership of this permission"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_1)
-    }
-
-    @Test
-    fun testOnPackageAdded_takingOverPermissionDeclaredBySystemApp_remainsUnchanged() {
-        testTakingOverPermissionAndPermissionGroupDefinitions(oldPermissionOwnerIsSystem = true)
-
-        assertWithMessage(
-            "After onPackageAdded() is called when $PERMISSION_NAME_0 already" +
-                " exists in system and is owned by a system app, the $PACKAGE_NAME_0 shouldn't" +
-                " takeover ownership of this permission"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_1)
-    }
-
-    private fun testTakingOverPermissionAndPermissionGroupDefinitions(
-        oldPermissionOwnerIsSystem: Boolean = false,
-        newPermissionOwnerIsSystem: Boolean = false,
-        newPermissionOwnerIsInstant: Boolean = false,
-        permissionGroupAlreadyExists: Boolean = true,
-        permissionAlreadyExists: Boolean = true,
-        type: Int = Permission.TYPE_MANIFEST,
-        isReconciled: Boolean = true,
-    ) {
-        val oldPermissionOwnerPackageState = mockPackageState(
-            APP_ID_1,
-            PACKAGE_NAME_1,
-            isSystem = oldPermissionOwnerIsSystem
-        )
-        addPackageState(oldPermissionOwnerPackageState)
-        if (permissionGroupAlreadyExists) {
-            addPermissionGroup(mockParsedPermissionGroup(PERMISSION_GROUP_NAME_0, PACKAGE_NAME_1))
-        }
-        if (permissionAlreadyExists) {
-            addPermission(
-                mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_1),
-                type = type,
-                isReconciled = isReconciled
-            )
-        }
-
-        mutateState {
-            val newPermissionOwnerPackageState = mockPackageState(
-                APP_ID_0,
-                mockSimpleAndroidPackage(),
-                isSystem = newPermissionOwnerIsSystem,
-                isInstantApp = newPermissionOwnerIsInstant
-            )
-            addPackageState(newPermissionOwnerPackageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(newPermissionOwnerPackageState)
-            }
-        }
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionGroupChanged_getsRevoked() {
-        testPermissionChanged(
-            oldPermissionGroup = PERMISSION_GROUP_NAME_1,
-            newPermissionGroup = PERMISSION_GROUP_NAME_0
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that has a permission group change" +
-                " for a permission it defines, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_protectionLevelChanged_getsRevoked() {
-        testPermissionChanged(newProtectionLevel = PermissionInfo.PROTECTION_INTERNAL)
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that has a protection level change" +
-                " for a permission it defines, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    private fun testPermissionChanged(
-        oldPermissionGroup: String? = null,
-        newPermissionGroup: String? = null,
-        newProtectionLevel: Int = PermissionInfo.PROTECTION_DANGEROUS
-    ) {
-        val oldPermission = mockParsedPermission(
-            PERMISSION_NAME_0,
-            PACKAGE_NAME_0,
-            group = oldPermissionGroup,
-            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS
-        )
-        val oldPackageState = mockPackageState(
-            APP_ID_0,
-            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(oldPermission))
-        )
-        addPackageState(oldPackageState)
-        addPermission(oldPermission)
-        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED)
-
-        mutateState {
-            val newPermission = mockParsedPermission(
-                PERMISSION_NAME_0,
-                PACKAGE_NAME_0,
-                group = newPermissionGroup,
-                protectionLevel = newProtectionLevel
-            )
-            val newPackageState = mockPackageState(
-                APP_ID_0,
-                mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(newPermission))
-            )
-            addPackageState(newPackageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(newPackageState)
-            }
-        }
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionTreeNoLongerDeclared_getsDefinitionRemoved() {
-        testPermissionDeclaration {}
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that no longer defines a permission" +
-                " tree, the permission tree: $PERMISSION_NAME_0 in system state should be removed"
-        )
-            .that(getPermissionTree(PERMISSION_NAME_0))
-            .isNull()
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionTreeByDisabledSystemPackage_remainsUnchanged() {
-        testPermissionDeclaration {
-            val disabledSystemPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
-            addDisabledSystemPackageState(disabledSystemPackageState)
-        }
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that no longer defines" +
-                " a permission tree while this permission tree is still defined by" +
-                " a disabled system package, the permission tree: $PERMISSION_NAME_0 in" +
-                " system state should not be removed"
-        )
-            .that(getPermissionTree(PERMISSION_TREE_NAME))
-            .isNotNull()
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionNoLongerDeclared_getsDefinitionRemoved() {
-        testPermissionDeclaration {}
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that no longer defines a permission," +
-                " the permission: $PERMISSION_NAME_0 in system state should be removed"
-        )
-            .that(getPermission(PERMISSION_NAME_0))
-            .isNull()
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionByDisabledSystemPackage_remainsUnchanged() {
-        testPermissionDeclaration {
-            val disabledSystemPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
-            addDisabledSystemPackageState(disabledSystemPackageState)
-        }
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a disabled system package and it's updated apk" +
-                " no longer defines a permission, the permission: $PERMISSION_NAME_0 in" +
-                " system state should not be removed"
-        )
-            .that(getPermission(PERMISSION_NAME_0))
-            .isNotNull()
-    }
-
-    private fun testPermissionDeclaration(additionalSetup: () -> Unit) {
-        val oldPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
-        addPackageState(oldPackageState)
-        addPermission(defaultPermissionTree)
-        addPermission(defaultPermission)
-
-        additionalSetup()
-
-        mutateState {
-            val newPackageState = mockPackageState(APP_ID_0, mockAndroidPackage(PACKAGE_NAME_0))
-            addPackageState(newPackageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(newPackageState)
-            }
-        }
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionsNoLongerRequested_getsFlagsRevoked() {
-        val parsedPermission = mockParsedPermission(
-            PERMISSION_NAME_0,
-            PACKAGE_NAME_0,
-            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS
-        )
-        val oldPackageState = mockPackageState(
-            APP_ID_0,
-            mockAndroidPackage(
-                PACKAGE_NAME_0,
-                permissions = listOf(parsedPermission),
-                requestedPermissions = setOf(PERMISSION_NAME_0)
-            )
-        )
-        addPackageState(oldPackageState)
-        addPermission(parsedPermission)
-        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED)
-
-        mutateState {
-            val newPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
-            addPackageState(newPackageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(newPackageState)
-            }
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that no longer requests a permission" +
-                " the actual permission flags $actualFlags should match the" +
-                " expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_storageAndMediaPermissionsDowngradingPastQ_getsRuntimeRevoked() {
-        testRevokePermissionsOnPackageUpdate(
-            PermissionFlags.RUNTIME_GRANTED,
-            newTargetSdkVersion = Build.VERSION_CODES.P
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that's downgrading past Q" +
-                " the actual permission flags $actualFlags should match the" +
-                " expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_storageAndMediaPermissionsNotDowngradingPastQ_remainsUnchanged() {
-        val oldFlags = PermissionFlags.RUNTIME_GRANTED
-        testRevokePermissionsOnPackageUpdate(
-            oldFlags,
-            oldTargetSdkVersion = Build.VERSION_CODES.P,
-            newTargetSdkVersion = Build.VERSION_CODES.P
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that's not downgrading past Q" +
-                " the actual permission flags $actualFlags should match the" +
-                " expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_policyFixedDowngradingPastQ_remainsUnchanged() {
-        val oldFlags = PermissionFlags.RUNTIME_GRANTED and PermissionFlags.POLICY_FIXED
-        testRevokePermissionsOnPackageUpdate(oldFlags, newTargetSdkVersion = Build.VERSION_CODES.P)
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that's downgrading past Q" +
-                " the actual permission flags with PermissionFlags.POLICY_FIXED $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_newlyRequestingLegacyExternalStorage_getsRuntimeRevoked() {
-        testRevokePermissionsOnPackageUpdate(
-            PermissionFlags.RUNTIME_GRANTED,
-            oldTargetSdkVersion = Build.VERSION_CODES.P,
-            newTargetSdkVersion = Build.VERSION_CODES.P,
-            oldIsRequestLegacyExternalStorage = false
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package with" +
-                " newlyRequestingLegacyExternalStorage, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_missingOldPackage_remainsUnchanged() {
-        val oldFlags = PermissionFlags.RUNTIME_GRANTED
-        testRevokePermissionsOnPackageUpdate(
-            oldFlags,
-            newTargetSdkVersion = Build.VERSION_CODES.P,
-            isOldPackageMissing = true
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that's downgrading past Q" +
-                " and doesn't have the oldPackage, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    private fun testRevokePermissionsOnPackageUpdate(
-        oldFlags: Int,
-        oldTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
-        newTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
-        oldIsRequestLegacyExternalStorage: Boolean = true,
-        newIsRequestLegacyExternalStorage: Boolean = true,
-        isOldPackageMissing: Boolean = false
-    ) {
-        val parsedPermission = mockParsedPermission(
-            PERMISSION_READ_EXTERNAL_STORAGE,
-            PACKAGE_NAME_0,
-            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS
-        )
-        val oldPackageState = if (isOldPackageMissing) {
-            mockPackageState(APP_ID_0, PACKAGE_NAME_0)
-        } else {
-            mockPackageState(
-                APP_ID_0,
-                mockAndroidPackage(
-                    PACKAGE_NAME_0,
-                    targetSdkVersion = oldTargetSdkVersion,
-                    isRequestLegacyExternalStorage = oldIsRequestLegacyExternalStorage,
-                    requestedPermissions = setOf(PERMISSION_READ_EXTERNAL_STORAGE),
-                    permissions = listOf(parsedPermission)
-                )
-            )
-        }
-        addPackageState(oldPackageState)
-        addPermission(parsedPermission)
-        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE, oldFlags)
-
-        mutateState {
-            val newPackageState = mockPackageState(
-                APP_ID_0,
-                mockAndroidPackage(
-                    PACKAGE_NAME_0,
-                    targetSdkVersion = newTargetSdkVersion,
-                    isRequestLegacyExternalStorage = newIsRequestLegacyExternalStorage,
-                    requestedPermissions = setOf(PERMISSION_READ_EXTERNAL_STORAGE),
-                    permissions = listOf(parsedPermission)
-                )
-            )
-            addPackageState(newPackageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(newPackageState)
-            }
-        }
-    }
-
-    @Test
-    fun testOnPackageAdded_normalPermissionAlreadyGranted_remainsUnchanged() {
-        val oldFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.INSTALL_REVOKED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_NORMAL) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a normal permission" +
-                " with an existing INSTALL_GRANTED flag, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_normalPermissionNotInstallRevoked_getsGranted() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_NORMAL,
-            isNewInstall = true
-        ) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a normal permission" +
-                " with no existing flags, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_normalPermissionRequestedByInstalledPackage_getsGranted() {
-        val oldFlags = PermissionFlags.INSTALL_REVOKED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_NORMAL) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a normal permission" +
-                " with the INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags since it's a new install"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    /**
-     * We setup a permission protection level change from SIGNATURE to NORMAL in order to make
-     * the permission a "changed permission" in order to test evaluatePermissionState() called by
-     * evaluatePermissionStateForAllPackages(). This makes the requestingPackageState not the
-     * installedPackageState so that we can test whether requesting by system package will give us
-     * the expected permission flags.
-     *
-     * Besides, this also helps us test evaluatePermissionStateForAllPackages(). Since both
-     * evaluatePermissionStateForAllPackages() and evaluateAllPermissionStatesForPackage() call
-     * evaluatePermissionState() in their implementations, we use these tests as the only tests
-     * that test evaluatePermissionStateForAllPackages()
-     */
-    @Test
-    fun testOnPackageAdded_normalPermissionRequestedBySystemPackage_getsGranted() {
-        testEvaluateNormalPermissionStateWithPermissionChanges(requestingPackageIsSystem = true)
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a system package that requests a normal" +
-                " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_normalCompatibilityPermission_getsGranted() {
-        testEvaluateNormalPermissionStateWithPermissionChanges(
-            permissionName = PERMISSION_POST_NOTIFICATIONS,
-            requestingPackageTargetSdkVersion = Build.VERSION_CODES.S
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_POST_NOTIFICATIONS)
-        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a normal compatibility" +
-                " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_normalPermissionPreviouslyRevoked_getsInstallRevoked() {
-        testEvaluateNormalPermissionStateWithPermissionChanges()
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.INSTALL_REVOKED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a normal" +
-                " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    private fun testEvaluateNormalPermissionStateWithPermissionChanges(
-        permissionName: String = PERMISSION_NAME_0,
-        requestingPackageTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
-        requestingPackageIsSystem: Boolean = false
-    ) {
-        val oldParsedPermission = mockParsedPermission(
-            permissionName,
-            PACKAGE_NAME_0,
-            protectionLevel = PermissionInfo.PROTECTION_SIGNATURE
-        )
-        val oldPermissionOwnerPackageState = mockPackageState(
-            APP_ID_0,
-            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(oldParsedPermission))
-        )
-        val requestingPackageState = mockPackageState(
-            APP_ID_1,
-            mockAndroidPackage(
-                PACKAGE_NAME_1,
-                requestedPermissions = setOf(permissionName),
-                targetSdkVersion = requestingPackageTargetSdkVersion
-            ),
-            isSystem = requestingPackageIsSystem,
-        )
-        addPackageState(oldPermissionOwnerPackageState)
-        addPackageState(requestingPackageState)
-        addPermission(oldParsedPermission)
-        val oldFlags = PermissionFlags.INSTALL_REVOKED
-        setPermissionFlags(APP_ID_1, USER_ID_0, permissionName, oldFlags)
-
-        mutateState {
-            val newPermissionOwnerPackageState = mockPackageState(
-                APP_ID_0,
-                mockAndroidPackage(
-                    PACKAGE_NAME_0,
-                    permissions = listOf(mockParsedPermission(permissionName, PACKAGE_NAME_0))
-                )
-            )
-            addPackageState(newPermissionOwnerPackageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(newPermissionOwnerPackageState)
-            }
-        }
-    }
-
-    @Test
-    fun testOnPackageAdded_normalAppOpPermission_getsRoleAndUserSetFlagsPreserved() {
-        val oldFlags = PermissionFlags.ROLE or PermissionFlags.USER_SET
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_NORMAL or PermissionInfo.PROTECTION_FLAG_APPOP
-        ) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED or oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a normal app op" +
-                " permission with existing ROLE and USER_SET flags, the actual permission flags" +
-                " $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_internalPermissionWasGrantedWithMissingPackage_getsProtectionGranted() {
-        val oldFlags = PermissionFlags.PROTECTION_GRANTED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_INTERNAL) {
-            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
-            addPackageState(packageStateWithMissingPackage)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests an internal permission" +
-                " with missing android package and $oldFlags flag, the actual permission flags" +
-                " $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_internalAppOpPermission_getsRoleAndUserSetFlagsPreserved() {
-        val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.ROLE or
-            PermissionFlags.USER_SET
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_APPOP
-        ) {
-            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
-            addPackageState(packageStateWithMissingPackage)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests an internal permission" +
-                " with missing android package and $oldFlags flag and the permission isAppOp," +
-                " the actual permission flags $actualFlags should match the expected" +
-                " flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_internalDevelopmentPermission_getsRuntimeGrantedPreserved() {
-        val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_DEVELOPMENT
-        ) {
-            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
-            addPackageState(packageStateWithMissingPackage)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests an internal permission" +
-                " with missing android package and $oldFlags flag and permission isDevelopment," +
-                " the actual permission flags $actualFlags should match the expected" +
-                " flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_internalRolePermission_getsRoleAndRuntimeGrantedPreserved() {
-        val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.ROLE or
-            PermissionFlags.RUNTIME_GRANTED
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_ROLE
-        ) {
-            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
-            addPackageState(packageStateWithMissingPackage)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests an internal permission" +
-                " with missing android package and $oldFlags flag and the permission isRole," +
-                " the actual permission flags $actualFlags should match the expected" +
-                " flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_signaturePrivilegedPermissionNotAllowlisted_isNotGranted() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_SIGNATURE or PermissionInfo.PROTECTION_FLAG_PRIVILEGED,
-            isInstalledPackageSystem = true,
-            isInstalledPackagePrivileged = true,
-            isInstalledPackageProduct = true,
-            // To mock the return value of shouldGrantPrivilegedOrOemPermission()
-            isInstalledPackageVendor = true,
-            isNewInstall = true
-        ) {
-            val platformPackage = mockPackageState(
-                PLATFORM_APP_ID,
-                mockAndroidPackage(PLATFORM_PACKAGE_NAME)
-            )
-            setupAllowlist(PACKAGE_NAME_1, false)
-            addPackageState(platformPackage)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a signature privileged" +
-                " permission that's not allowlisted, the actual permission" +
-                " flags $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_nonPrivilegedPermissionShouldGrantBySignature_getsProtectionGranted() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_SIGNATURE,
-            isInstalledPackageSystem = true,
-            isInstalledPackagePrivileged = true,
-            isInstalledPackageProduct = true,
-            isInstalledPackageSignatureMatching = true,
-            isInstalledPackageVendor = true,
-            isNewInstall = true
-        ) {
-            val platformPackage = mockPackageState(
-                PLATFORM_APP_ID,
-                mockAndroidPackage(PLATFORM_PACKAGE_NAME, isSignatureMatching = true)
-            )
-            setupAllowlist(PACKAGE_NAME_1, false)
-            addPackageState(platformPackage)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.PROTECTION_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a signature" +
-                " non-privileged permission, the actual permission" +
-                " flags $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_privilegedAllowlistPermissionShouldGrantByProtectionFlags_getsGranted() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_SIGNATURE or PermissionInfo.PROTECTION_FLAG_PRIVILEGED,
-            isInstalledPackageSystem = true,
-            isInstalledPackagePrivileged = true,
-            isInstalledPackageProduct = true,
-            isNewInstall = true
-        ) {
-            val platformPackage = mockPackageState(
-                PLATFORM_APP_ID,
-                mockAndroidPackage(PLATFORM_PACKAGE_NAME)
-            )
-            setupAllowlist(PACKAGE_NAME_1, true)
-            addPackageState(platformPackage)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.PROTECTION_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a signature privileged" +
-                " permission that's allowlisted and should grant by protection flags, the actual" +
-                " permission flags $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    private fun setupAllowlist(
-        packageName: String,
-        allowlistState: Boolean,
-        state: MutableAccessState = oldState
-    ) {
-        state.mutateExternalState().setPrivilegedPermissionAllowlistPackages(
-            MutableIndexedListSet<String>().apply { add(packageName) }
-        )
-        val mockAllowlist = mock<PermissionAllowlist> {
-            whenever(
-                getProductPrivilegedAppAllowlistState(packageName, PERMISSION_NAME_0)
-            ).thenReturn(allowlistState)
-        }
-        state.mutateExternalState().setPermissionAllowlist(mockAllowlist)
-    }
-
-    @Test
-    fun testOnPackageAdded_nonRuntimeFlagsOnRuntimePermissions_getsCleared() {
-        val oldFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.PREGRANT or
-            PermissionFlags.RUNTIME_GRANTED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.PREGRANT or PermissionFlags.RUNTIME_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " with existing $oldFlags flags, the actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_newPermissionsForPreM_requiresUserReview() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_DANGEROUS,
-            installedPackageTargetSdkVersion = Build.VERSION_CODES.LOLLIPOP,
-            isNewInstall = true
-        ) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " with no existing flags in pre M, actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_legacyOrImplicitGrantedPermissionPreviouslyRevoked_getsAppOpRevoked() {
-        val oldFlags = PermissionFlags.USER_FIXED
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_DANGEROUS,
-            installedPackageTargetSdkVersion = Build.VERSION_CODES.LOLLIPOP
-        ) {
-            setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.USER_FIXED or
-            PermissionFlags.APP_OP_REVOKED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " that should be LEGACY_GRANTED or IMPLICIT_GRANTED that was previously revoked," +
-                " the actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_legacyGrantedPermissionsForPostM_userReviewRequirementRemoved() {
-        val oldFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " that used to require user review, the user review requirement should be removed" +
-                " if it's upgraded to post M. The actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_legacyGrantedPermissionsAlreadyReviewedForPostM_getsGranted() {
-        val oldFlags = PermissionFlags.LEGACY_GRANTED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " that was already reviewed by the user, the permission should be RUNTIME_GRANTED" +
-                " if it's upgraded to post M. The actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_leanbackNotificationPermissionsForPostM_getsImplicitGranted() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_DANGEROUS,
-            permissionName = PERMISSION_POST_NOTIFICATIONS,
-            isNewInstall = true
-        ) {
-            oldState.mutateExternalState().setLeanback(true)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_POST_NOTIFICATIONS)
-        val expectedNewFlags = PermissionFlags.IMPLICIT_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime notification" +
-                " permission when isLeanback, the actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_implicitSourceFromNonRuntime_getsImplicitGranted() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_DANGEROUS,
-            implicitPermissions = setOf(PERMISSION_NAME_0),
-            isNewInstall = true
-        ) {
-            oldState.mutateExternalState().setImplicitToSourcePermissions(
-                MutableIndexedMap<String, IndexedListSet<String>>().apply {
-                    put(PERMISSION_NAME_0, MutableIndexedListSet<String>().apply {
-                        add(PERMISSION_NAME_1)
-                    })
-                }
-            )
-            addPermission(mockParsedPermission(PERMISSION_NAME_1, PACKAGE_NAME_0))
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime implicit" +
-                " permission that's source from a non-runtime permission, the actual permission" +
-                " flags $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    /**
-     * For a legacy granted or implicit permission during the app upgrade, when the permission
-     * should no longer be legacy or implicit granted, we want to remove the APP_OP_REVOKED flag
-     * so that the app can request the permission.
-     */
-    @Test
-    fun testOnPackageAdded_noLongerLegacyOrImplicitGranted_canBeRequested() {
-        val oldFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.APP_OP_REVOKED or
-            PermissionFlags.RUNTIME_GRANTED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " that is no longer LEGACY_GRANTED or IMPLICIT_GRANTED, the actual permission" +
-                " flags $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_noLongerImplicitPermissions_getsRuntimeAndImplicitFlagsRemoved() {
-        val oldFlags = PermissionFlags.IMPLICIT or PermissionFlags.RUNTIME_GRANTED or
-            PermissionFlags.USER_SET or PermissionFlags.USER_FIXED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " that is no longer implicit and we shouldn't retain as nearby device" +
-                " permissions, the actual permission flags $actualFlags should match the expected" +
-                " flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_noLongerImplicitNearbyPermissionsWasGranted_getsRuntimeGranted() {
-        val oldFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_DANGEROUS,
-            permissionName = PERMISSION_BLUETOOTH_CONNECT,
-            requestedPermissions = setOf(
-                PERMISSION_BLUETOOTH_CONNECT,
-                PERMISSION_ACCESS_BACKGROUND_LOCATION
-            )
-        ) {
-            setPermissionFlags(
-                APP_ID_1,
-                USER_ID_0,
-                PERMISSION_ACCESS_BACKGROUND_LOCATION,
-                PermissionFlags.RUNTIME_GRANTED
-            )
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_BLUETOOTH_CONNECT)
-        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime nearby device" +
-                " permission that was granted by implicit, the actual permission flags" +
-                " $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_noLongerImplicitSystemOrPolicyFixedWasGranted_getsRuntimeGranted() {
-        val oldFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT or
-            PermissionFlags.SYSTEM_FIXED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.SYSTEM_FIXED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " that was granted and is no longer implicit and is SYSTEM_FIXED or POLICY_FIXED," +
-                " the actual permission flags $actualFlags should match the expected" +
-                " flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_restrictedPermissionsNotExempt_getsRestrictionFlags() {
-        val oldFlags = PermissionFlags.RESTRICTION_REVOKED
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_DANGEROUS,
-            permissionInfoFlags = PermissionInfo.FLAG_HARD_RESTRICTED
-        ) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime hard" +
-                " restricted permission that is not exempted, the actual permission flags" +
-                " $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_restrictedPermissionsIsExempted_clearsRestrictionFlags() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_DANGEROUS,
-            permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED
-        ) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.UPGRADE_EXEMPT
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime soft" +
-                " restricted permission that is exempted, the actual permission flags" +
-                " $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_runtimeExistingImplicitPermissions_sourceFlagsNotInherited() {
-        val oldImplicitPermissionFlags = PermissionFlags.USER_FIXED
-        testInheritImplicitPermissionStates(
-            implicitPermissionFlags = oldImplicitPermissionFlags,
-            isNewInstallAndNewPermission = false
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldImplicitPermissionFlags or PermissionFlags.IMPLICIT_GRANTED or
-            PermissionFlags.APP_OP_REVOKED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a permission that is" +
-                " implicit, existing and runtime, it should not inherit the runtime flags from" +
-                " the source permission. Hence the actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_nonRuntimeNewImplicitPermissions_sourceFlagsNotInherited() {
-        testInheritImplicitPermissionStates(
-            implicitPermissionProtectionLevel = PermissionInfo.PROTECTION_NORMAL
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a permission that is" +
-                " implicit, new and non-runtime, it should not inherit the runtime flags from" +
-                " the source permission. Hence the actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_runtimeNewImplicitPermissions_sourceFlagsInherited() {
-        val sourceRuntimeFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET
-        testInheritImplicitPermissionStates(sourceRuntimeFlags = sourceRuntimeFlags)
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = sourceRuntimeFlags or PermissionFlags.IMPLICIT_GRANTED or
-            PermissionFlags.IMPLICIT
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a permission that is" +
-                " implicit, new and runtime, it should inherit the runtime flags from" +
-                " the source permission. Hence the actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_grantingNewFromRevokeImplicitPermissions_onlySourceFlagsInherited() {
-        val sourceRuntimeFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET
-        testInheritImplicitPermissionStates(
-            implicitPermissionFlags = PermissionFlags.POLICY_FIXED,
-            sourceRuntimeFlags = sourceRuntimeFlags,
-            isAnySourcePermissionNonRuntime = false
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = sourceRuntimeFlags or PermissionFlags.IMPLICIT
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a permission that is" +
-                " implicit, existing, runtime and revoked, it should only inherit runtime flags" +
-                " from source permission. Hence the actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    /**
-     * If it's a media implicit permission (one of RETAIN_IMPLICIT_FLAGS_PERMISSIONS), we want to
-     * remove the IMPLICIT flag so that they will be granted when they are no longer implicit.
-     * (instead of revoking it)
-     */
-    @Test
-    fun testOnPackageAdded_mediaImplicitPermissions_getsImplicitFlagRemoved() {
-        val sourceRuntimeFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET
-        testInheritImplicitPermissionStates(
-            implicitPermissionName = PERMISSION_ACCESS_MEDIA_LOCATION,
-            sourceRuntimeFlags = sourceRuntimeFlags
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_ACCESS_MEDIA_LOCATION)
-        val expectedNewFlags = sourceRuntimeFlags or PermissionFlags.IMPLICIT_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a media permission that" +
-                " is implicit, new and runtime, it should inherit the runtime flags from" +
-                " the source permission and have the IMPLICIT flag removed. Hence the actual" +
-                " permission flags $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    private fun testInheritImplicitPermissionStates(
-        implicitPermissionName: String = PERMISSION_NAME_0,
-        implicitPermissionFlags: Int = 0,
-        implicitPermissionProtectionLevel: Int = PermissionInfo.PROTECTION_DANGEROUS,
-        sourceRuntimeFlags: Int = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET,
-        isAnySourcePermissionNonRuntime: Boolean = true,
-        isNewInstallAndNewPermission: Boolean = true
-    ) {
-        val implicitPermission = mockParsedPermission(
-            implicitPermissionName,
-            PACKAGE_NAME_0,
-            protectionLevel = implicitPermissionProtectionLevel,
-        )
-        // For source from non-runtime in order to grant by implicit
-        val sourcePermission1 = mockParsedPermission(
-            PERMISSION_NAME_1,
-            PACKAGE_NAME_0,
-            protectionLevel = if (isAnySourcePermissionNonRuntime) {
-                PermissionInfo.PROTECTION_NORMAL
-            } else {
-                PermissionInfo.PROTECTION_DANGEROUS
-            }
-        )
-        // For inheriting runtime flags
-        val sourcePermission2 = mockParsedPermission(
-            PERMISSION_NAME_2,
-            PACKAGE_NAME_0,
-            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS,
-        )
-        val permissionOwnerPackageState = mockPackageState(
-            APP_ID_0,
-            mockAndroidPackage(
-                PACKAGE_NAME_0,
-                permissions = listOf(implicitPermission, sourcePermission1, sourcePermission2)
-            )
-        )
-        val installedPackageState = mockPackageState(
-            APP_ID_1,
-            mockAndroidPackage(
-                PACKAGE_NAME_1,
-                requestedPermissions = setOf(
-                    implicitPermissionName,
-                    PERMISSION_NAME_1,
-                    PERMISSION_NAME_2
-                ),
-                implicitPermissions = setOf(implicitPermissionName)
-            )
-        )
-        oldState.mutateExternalState().setImplicitToSourcePermissions(
-            MutableIndexedMap<String, IndexedListSet<String>>().apply {
-                put(implicitPermissionName, MutableIndexedListSet<String>().apply {
-                    add(PERMISSION_NAME_1)
-                    add(PERMISSION_NAME_2)
-                })
-            }
-        )
-        addPackageState(permissionOwnerPackageState)
-        addPermission(implicitPermission)
-        addPermission(sourcePermission1)
-        addPermission(sourcePermission2)
-        if (!isNewInstallAndNewPermission) {
-            addPackageState(installedPackageState)
-            setPermissionFlags(APP_ID_1, USER_ID_0, implicitPermissionName, implicitPermissionFlags)
-        }
-        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_2, sourceRuntimeFlags)
-
-        mutateState {
-            if (isNewInstallAndNewPermission) {
-                addPackageState(installedPackageState)
-                setPermissionFlags(
-                    APP_ID_1,
-                    USER_ID_0,
-                    implicitPermissionName,
-                    implicitPermissionFlags,
-                    newState
-                )
-            }
-            with(appIdPermissionPolicy) {
-                onPackageAdded(installedPackageState)
-            }
-        }
-    }
-
-    /**
-     * Setup simple package states for testing evaluatePermissionState().
-     * permissionOwnerPackageState is definer of permissionName with APP_ID_0.
-     * installedPackageState is the installed package that requests permissionName with APP_ID_1.
-     *
-     * @param oldFlags the existing permission flags for APP_ID_1, USER_ID_0, permissionName
-     * @param protectionLevel the protectionLevel for the permission
-     * @param permissionName the name of the permission (1) being defined (2) of the oldFlags, and
-     *                       (3) requested by installedPackageState
-     * @param requestedPermissions the permissions requested by installedPackageState
-     * @param implicitPermissions the implicit permissions of installedPackageState
-     * @param permissionInfoFlags the flags for the permission itself
-     * @param isInstalledPackageSystem whether installedPackageState is a system package
-     *
-     * @return installedPackageState
-     */
-    fun testEvaluatePermissionState(
-        oldFlags: Int,
-        protectionLevel: Int,
-        permissionName: String = PERMISSION_NAME_0,
-        requestedPermissions: Set<String> = setOf(permissionName),
-        implicitPermissions: Set<String> = emptySet(),
-        permissionInfoFlags: Int = 0,
-        isInstalledPackageSystem: Boolean = false,
-        isInstalledPackagePrivileged: Boolean = false,
-        isInstalledPackageProduct: Boolean = false,
-        isInstalledPackageSignatureMatching: Boolean = false,
-        isInstalledPackageVendor: Boolean = false,
-        installedPackageTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
-        isNewInstall: Boolean = false,
-        additionalSetup: () -> Unit
-    ) {
-        val parsedPermission = mockParsedPermission(
-            permissionName,
-            PACKAGE_NAME_0,
-            protectionLevel = protectionLevel,
-            flags = permissionInfoFlags
-        )
-        val permissionOwnerPackageState = mockPackageState(
-            APP_ID_0,
-            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
-        )
-        val installedPackageState = mockPackageState(
-            APP_ID_1,
-            mockAndroidPackage(
-                PACKAGE_NAME_1,
-                requestedPermissions = requestedPermissions,
-                implicitPermissions = implicitPermissions,
-                targetSdkVersion = installedPackageTargetSdkVersion,
-                isSignatureMatching = isInstalledPackageSignatureMatching
-            ),
-            isSystem = isInstalledPackageSystem,
-            isPrivileged = isInstalledPackagePrivileged,
-            isProduct = isInstalledPackageProduct,
-            isVendor = isInstalledPackageVendor
-        )
-        addPackageState(permissionOwnerPackageState)
-        if (!isNewInstall) {
-            addPackageState(installedPackageState)
-            setPermissionFlags(APP_ID_1, USER_ID_0, permissionName, oldFlags)
-        }
-        addPermission(parsedPermission)
-
-        additionalSetup()
-
-        mutateState {
-            if (isNewInstall) {
-                addPackageState(installedPackageState, newState)
-                setPermissionFlags(APP_ID_1, USER_ID_0, permissionName, oldFlags, newState)
-            }
-            with(appIdPermissionPolicy) {
-                onPackageAdded(installedPackageState)
-            }
-        }
-    }
-
-    /**
-     * Mock an AndroidPackage with PACKAGE_NAME_0, PERMISSION_NAME_0 and PERMISSION_GROUP_NAME_0
-     */
-    private fun mockSimpleAndroidPackage(): AndroidPackage =
-        mockAndroidPackage(
-            PACKAGE_NAME_0,
-            permissionGroups = listOf(defaultPermissionGroup),
-            permissions = listOf(defaultPermissionTree, defaultPermission)
-        )
-
-    private inline fun mutateState(action: MutateStateScope.() -> Unit) {
-        newState = oldState.toMutable()
-        MutateStateScope(oldState, newState).action()
-    }
-
-    private fun mockPackageState(
-        appId: Int,
-        packageName: String,
-        isSystem: Boolean = false,
-    ): PackageState =
-        mock {
-            whenever(this.appId).thenReturn(appId)
-            whenever(this.packageName).thenReturn(packageName)
-            whenever(androidPackage).thenReturn(null)
-            whenever(this.isSystem).thenReturn(isSystem)
-        }
-
-    private fun mockPackageState(
-        appId: Int,
-        androidPackage: AndroidPackage,
-        isSystem: Boolean = false,
-        isPrivileged: Boolean = false,
-        isProduct: Boolean = false,
-        isInstantApp: Boolean = false,
-        isVendor: Boolean = false
-    ): PackageState =
-        mock {
-            whenever(this.appId).thenReturn(appId)
-            whenever(this.androidPackage).thenReturn(androidPackage)
-            val packageName = androidPackage.packageName
-            whenever(this.packageName).thenReturn(packageName)
-            whenever(this.isSystem).thenReturn(isSystem)
-            whenever(this.isPrivileged).thenReturn(isPrivileged)
-            whenever(this.isProduct).thenReturn(isProduct)
-            whenever(this.isVendor).thenReturn(isVendor)
-            val userStates = SparseArray<PackageUserState>().apply {
-                put(USER_ID_0, mock { whenever(this.isInstantApp).thenReturn(isInstantApp) })
-            }
-            whenever(this.userStates).thenReturn(userStates)
-        }
-
-    private fun mockAndroidPackage(
-        packageName: String,
-        targetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
-        isRequestLegacyExternalStorage: Boolean = false,
-        adoptPermissions: List<String> = emptyList(),
-        implicitPermissions: Set<String> = emptySet(),
-        requestedPermissions: Set<String> = emptySet(),
-        permissionGroups: List<ParsedPermissionGroup> = emptyList(),
-        permissions: List<ParsedPermission> = emptyList(),
-        isSignatureMatching: Boolean = false
-    ): AndroidPackage =
-        mock {
-            whenever(this.packageName).thenReturn(packageName)
-            whenever(this.targetSdkVersion).thenReturn(targetSdkVersion)
-            whenever(this.isRequestLegacyExternalStorage).thenReturn(isRequestLegacyExternalStorage)
-            whenever(this.adoptPermissions).thenReturn(adoptPermissions)
-            whenever(this.implicitPermissions).thenReturn(implicitPermissions)
-            whenever(this.requestedPermissions).thenReturn(requestedPermissions)
-            whenever(this.permissionGroups).thenReturn(permissionGroups)
-            whenever(this.permissions).thenReturn(permissions)
-            val signingDetails = mock<SigningDetails> {
-                whenever(
-                    hasCommonSignerWithCapability(any(), any())
-                ).thenReturn(isSignatureMatching)
-                whenever(hasAncestorOrSelf(any())).thenReturn(isSignatureMatching)
-                whenever(
-                    checkCapability(any<SigningDetails>(), any())
-                ).thenReturn(isSignatureMatching)
-            }
-            whenever(this.signingDetails).thenReturn(signingDetails)
-        }
-
-    private fun mockParsedPermission(
-        permissionName: String,
-        packageName: String,
-        backgroundPermission: String? = null,
-        group: String? = null,
-        protectionLevel: Int = PermissionInfo.PROTECTION_NORMAL,
-        flags: Int = 0,
-        isTree: Boolean = false
-    ): ParsedPermission =
-        mock {
-            whenever(name).thenReturn(permissionName)
-            whenever(this.packageName).thenReturn(packageName)
-            whenever(metaData).thenReturn(Bundle())
-            whenever(this.backgroundPermission).thenReturn(backgroundPermission)
-            whenever(this.group).thenReturn(group)
-            whenever(this.protectionLevel).thenReturn(protectionLevel)
-            whenever(this.flags).thenReturn(flags)
-            whenever(this.isTree).thenReturn(isTree)
-        }
-
-    private fun mockParsedPermissionGroup(
-        permissionGroupName: String,
-        packageName: String,
-    ): ParsedPermissionGroup =
-        mock {
-            whenever(name).thenReturn(permissionGroupName)
-            whenever(this.packageName).thenReturn(packageName)
-            whenever(metaData).thenReturn(Bundle())
-        }
-
-    private fun addPackageState(packageState: PackageState, state: MutableAccessState = oldState) {
-        state.mutateExternalState().apply {
-            setPackageStates(
-                packageStates.toMutableMap().apply {
-                    put(packageState.packageName, packageState)
-                }
-            )
-            mutateAppIdPackageNames().mutateOrPut(packageState.appId) { MutableIndexedListSet() }
-                .add(packageState.packageName)
-        }
-    }
-
-    private fun addDisabledSystemPackageState(
-        packageState: PackageState,
-        state: MutableAccessState = oldState
-    ) = state.mutateExternalState().apply {
-        (disabledSystemPackageStates as ArrayMap)[packageState.packageName] = packageState
-    }
-
-    private fun addPermission(
-        parsedPermission: ParsedPermission,
-        type: Int = Permission.TYPE_MANIFEST,
-        isReconciled: Boolean = true,
-        state: MutableAccessState = oldState
-    ) {
-        val permissionInfo = PackageInfoUtils.generatePermissionInfo(
-            parsedPermission,
-            PackageManager.GET_META_DATA.toLong()
-        )!!
-        val appId = state.externalState.packageStates[permissionInfo.packageName]!!.appId
-        val permission = Permission(permissionInfo, isReconciled, type, appId)
-        if (parsedPermission.isTree) {
-            state.mutateSystemState().mutatePermissionTrees()[permission.name] = permission
-        } else {
-            state.mutateSystemState().mutatePermissions()[permission.name] = permission
-        }
-    }
-
-    private fun addPermissionGroup(
-        parsedPermissionGroup: ParsedPermissionGroup,
-        state: MutableAccessState = oldState
-    ) {
-        state.mutateSystemState().mutatePermissionGroups()[parsedPermissionGroup.name] =
-            PackageInfoUtils.generatePermissionGroupInfo(
-                parsedPermissionGroup,
-                PackageManager.GET_META_DATA.toLong()
-            )!!
-    }
-
-    private fun getPermission(
-        permissionName: String,
-        state: MutableAccessState = newState
-    ): Permission? = state.systemState.permissions[permissionName]
-
-    private fun getPermissionTree(
-        permissionTreeName: String,
-        state: MutableAccessState = newState
-    ): Permission? = state.systemState.permissionTrees[permissionTreeName]
-
-    private fun getPermissionGroup(
-        permissionGroupName: String,
-        state: MutableAccessState = newState
-    ): PermissionGroupInfo? = state.systemState.permissionGroups[permissionGroupName]
-
-    private fun getPermissionFlags(
-        appId: Int,
-        userId: Int,
-        permissionName: String,
-        state: MutableAccessState = newState
-    ): Int =
-        state.userStates[userId]?.appIdPermissionFlags?.get(appId).getWithDefault(permissionName, 0)
-
-    private fun setPermissionFlags(
-        appId: Int,
-        userId: Int,
-        permissionName: String,
-        flags: Int,
-        state: MutableAccessState = oldState
-    ) =
-        state.mutateUserState(userId)!!.mutateAppIdPermissionFlags().mutateOrPut(appId) {
-            MutableIndexedMap()
-        }.put(permissionName, flags)
-
-    companion object {
-        private const val PACKAGE_NAME_0 = "packageName0"
-        private const val PACKAGE_NAME_1 = "packageName1"
-        private const val MISSING_ANDROID_PACKAGE = "missingAndroidPackage"
-        private const val PLATFORM_PACKAGE_NAME = "android"
-
-        private const val APP_ID_0 = 0
-        private const val APP_ID_1 = 1
-        private const val PLATFORM_APP_ID = 2
-
-        private const val PERMISSION_GROUP_NAME_0 = "permissionGroupName0"
-        private const val PERMISSION_GROUP_NAME_1 = "permissionGroupName1"
-
-        private const val PERMISSION_TREE_NAME = "permissionTree"
-
-        private const val PERMISSION_NAME_0 = "permissionName0"
-        private const val PERMISSION_NAME_1 = "permissionName1"
-        private const val PERMISSION_NAME_2 = "permissionName2"
-        private const val PERMISSION_READ_EXTERNAL_STORAGE =
-            Manifest.permission.READ_EXTERNAL_STORAGE
-        private const val PERMISSION_POST_NOTIFICATIONS =
-            Manifest.permission.POST_NOTIFICATIONS
-        private const val PERMISSION_BLUETOOTH_CONNECT =
-            Manifest.permission.BLUETOOTH_CONNECT
-        private const val PERMISSION_ACCESS_BACKGROUND_LOCATION =
-            Manifest.permission.ACCESS_BACKGROUND_LOCATION
-        private const val PERMISSION_ACCESS_MEDIA_LOCATION =
-            Manifest.permission.ACCESS_MEDIA_LOCATION
-
-        private const val USER_ID_0 = 0
-    }
-}
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt
new file mode 100644
index 0000000..7966c5c
--- /dev/null
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.test
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.content.pm.PermissionGroupInfo
+import android.content.pm.PermissionInfo
+import android.content.pm.SigningDetails
+import android.os.Build
+import android.os.Bundle
+import android.util.ArrayMap
+import android.util.SparseArray
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.server.extendedtestutils.wheneverStatic
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutableUserState
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.permission.AppIdPermissionPolicy
+import com.android.server.permission.access.permission.Permission
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.permission.access.util.hasBits
+import com.android.server.pm.parsing.PackageInfoUtils
+import com.android.server.pm.pkg.AndroidPackage
+import com.android.server.pm.pkg.PackageState
+import com.android.server.pm.pkg.PackageUserState
+import com.android.server.pm.pkg.component.ParsedPermission
+import com.android.server.pm.pkg.component.ParsedPermissionGroup
+import com.android.server.testutils.any
+import com.android.server.testutils.mock
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyLong
+
+/**
+ * Mocking unit test for AppIdPermissionPolicy.
+ */
+@RunWith(AndroidJUnit4::class)
+open class BaseAppIdPermissionPolicyTest {
+    protected lateinit var oldState: MutableAccessState
+    protected lateinit var newState: MutableAccessState
+
+    protected val defaultPermissionGroup = mockParsedPermissionGroup(
+        PERMISSION_GROUP_NAME_0,
+        PACKAGE_NAME_0
+    )
+    protected val defaultPermissionTree = mockParsedPermission(
+        PERMISSION_TREE_NAME,
+        PACKAGE_NAME_0,
+        isTree = true
+    )
+    protected val defaultPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0)
+
+    protected val appIdPermissionPolicy = AppIdPermissionPolicy()
+
+    @Rule
+    @JvmField
+    val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
+        .spyStatic(PackageInfoUtils::class.java)
+        .build()
+
+    @Before
+    open fun setUp() {
+        oldState = MutableAccessState()
+        createUserState(USER_ID_0)
+        oldState.mutateExternalState().setPackageStates(ArrayMap())
+        oldState.mutateExternalState().setDisabledSystemPackageStates(ArrayMap())
+        mockPackageInfoUtilsGeneratePermissionInfo()
+        mockPackageInfoUtilsGeneratePermissionGroupInfo()
+    }
+
+    protected fun createUserState(userId: Int) {
+        oldState.mutateExternalState().mutateUserIds().add(userId)
+        oldState.mutateUserStatesNoWrite().put(userId, MutableUserState())
+    }
+
+    private fun mockPackageInfoUtilsGeneratePermissionInfo() {
+        wheneverStatic {
+            PackageInfoUtils.generatePermissionInfo(any(ParsedPermission::class.java), anyLong())
+        }.thenAnswer { invocation ->
+            val parsedPermission = invocation.getArgument<ParsedPermission>(0)
+            val generateFlags = invocation.getArgument<Long>(1)
+            PermissionInfo(parsedPermission.backgroundPermission).apply {
+                name = parsedPermission.name
+                packageName = parsedPermission.packageName
+                metaData = if (generateFlags.toInt().hasBits(PackageManager.GET_META_DATA)) {
+                    parsedPermission.metaData
+                } else {
+                    null
+                }
+                @Suppress("DEPRECATION")
+                protectionLevel = parsedPermission.protectionLevel
+                group = parsedPermission.group
+                flags = parsedPermission.flags
+            }
+        }
+    }
+
+    private fun mockPackageInfoUtilsGeneratePermissionGroupInfo() {
+        wheneverStatic {
+            PackageInfoUtils.generatePermissionGroupInfo(
+                any(ParsedPermissionGroup::class.java),
+                anyLong()
+            )
+        }.thenAnswer { invocation ->
+            val parsedPermissionGroup = invocation.getArgument<ParsedPermissionGroup>(0)
+            val generateFlags = invocation.getArgument<Long>(1)
+            @Suppress("DEPRECATION")
+            PermissionGroupInfo().apply {
+                name = parsedPermissionGroup.name
+                packageName = parsedPermissionGroup.packageName
+                metaData = if (generateFlags.toInt().hasBits(PackageManager.GET_META_DATA)) {
+                    parsedPermissionGroup.metaData
+                } else {
+                    null
+                }
+                flags = parsedPermissionGroup.flags
+            }
+        }
+    }
+
+    @Test
+    fun testOnAppIdRemoved_appIdIsRemoved_permissionFlagsCleared() {
+        val parsedPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0)
+        val permissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
+        )
+        val requestingPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+        )
+        addPackageState(permissionOwnerPackageState)
+        addPackageState(requestingPackageState)
+        addPermission(parsedPermission)
+        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.INSTALL_GRANTED)
+
+        mutateState {
+            with(appIdPermissionPolicy) {
+                onAppIdRemoved(APP_ID_1)
+            }
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After onAppIdRemoved() is called for appId $APP_ID_1 that requests a permission" +
+                " owns by appId $APP_ID_0 with existing permission flags. The actual permission" +
+                " flags $actualFlags should be null"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testOnPackageRemoved_packageIsRemoved_permissionsAreTrimmedAndStatesAreEvaluated() {
+        // TODO
+        // shouldn't reuse test cases because it's really different despite it's also for
+        // trim permission states. It's different because it's package removal
+    }
+
+    @Test
+    fun testOnPackageInstalled_nonSystemAppIsInstalled_upgradeExemptFlagIsCleared() {
+        // TODO
+        // should be fine for it to be its own test cases and not to re-use
+        // clearRestrictedPermissionImplicitExemption
+    }
+
+    @Test
+    fun testOnPackageInstalled_systemAppIsInstalled_upgradeExemptFlagIsRetained() {
+        // TODO
+    }
+
+    @Test
+    fun testOnPackageInstalled_requestedPermissionAlsoRequestedBySystemApp_exemptFlagIsRetained() {
+        // TODO
+    }
+
+    @Test
+    fun testOnPackageInstalled_restrictedPermissionsNotExempt_getsRestrictionFlags() {
+        // TODO
+    }
+
+    @Test
+    fun testOnPackageInstalled_restrictedPermissionsIsExempted_clearsRestrictionFlags() {
+        // TODO
+    }
+
+    @Test
+    fun testOnStateMutated_notEmpty_isCalledForEachListener() {
+        // TODO
+    }
+
+    /**
+     * Mock an AndroidPackage with PACKAGE_NAME_0, PERMISSION_NAME_0 and PERMISSION_GROUP_NAME_0
+     */
+    protected fun mockSimpleAndroidPackage(): AndroidPackage =
+        mockAndroidPackage(
+            PACKAGE_NAME_0,
+            permissionGroups = listOf(defaultPermissionGroup),
+            permissions = listOf(defaultPermissionTree, defaultPermission)
+        )
+
+    protected inline fun mutateState(action: MutateStateScope.() -> Unit) {
+        newState = oldState.toMutable()
+        MutateStateScope(oldState, newState).action()
+    }
+
+    protected fun mockPackageState(
+        appId: Int,
+        packageName: String,
+        isSystem: Boolean = false,
+    ): PackageState =
+        mock {
+            whenever(this.appId).thenReturn(appId)
+            whenever(this.packageName).thenReturn(packageName)
+            whenever(androidPackage).thenReturn(null)
+            whenever(this.isSystem).thenReturn(isSystem)
+        }
+
+    protected fun mockPackageState(
+        appId: Int,
+        androidPackage: AndroidPackage,
+        isSystem: Boolean = false,
+        isPrivileged: Boolean = false,
+        isProduct: Boolean = false,
+        isInstantApp: Boolean = false,
+        isVendor: Boolean = false
+    ): PackageState =
+        mock {
+            whenever(this.appId).thenReturn(appId)
+            whenever(this.androidPackage).thenReturn(androidPackage)
+            val packageName = androidPackage.packageName
+            whenever(this.packageName).thenReturn(packageName)
+            whenever(this.isSystem).thenReturn(isSystem)
+            whenever(this.isPrivileged).thenReturn(isPrivileged)
+            whenever(this.isProduct).thenReturn(isProduct)
+            whenever(this.isVendor).thenReturn(isVendor)
+            val userStates = SparseArray<PackageUserState>().apply {
+                put(USER_ID_0, mock { whenever(this.isInstantApp).thenReturn(isInstantApp) })
+            }
+            whenever(this.userStates).thenReturn(userStates)
+        }
+
+    protected fun mockAndroidPackage(
+        packageName: String,
+        targetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+        isRequestLegacyExternalStorage: Boolean = false,
+        adoptPermissions: List<String> = emptyList(),
+        implicitPermissions: Set<String> = emptySet(),
+        requestedPermissions: Set<String> = emptySet(),
+        permissionGroups: List<ParsedPermissionGroup> = emptyList(),
+        permissions: List<ParsedPermission> = emptyList(),
+        isSignatureMatching: Boolean = false
+    ): AndroidPackage =
+        mock {
+            whenever(this.packageName).thenReturn(packageName)
+            whenever(this.targetSdkVersion).thenReturn(targetSdkVersion)
+            whenever(this.isRequestLegacyExternalStorage).thenReturn(isRequestLegacyExternalStorage)
+            whenever(this.adoptPermissions).thenReturn(adoptPermissions)
+            whenever(this.implicitPermissions).thenReturn(implicitPermissions)
+            whenever(this.requestedPermissions).thenReturn(requestedPermissions)
+            whenever(this.permissionGroups).thenReturn(permissionGroups)
+            whenever(this.permissions).thenReturn(permissions)
+            val signingDetails = mock<SigningDetails> {
+                whenever(
+                    hasCommonSignerWithCapability(any(), any())
+                ).thenReturn(isSignatureMatching)
+                whenever(hasAncestorOrSelf(any())).thenReturn(isSignatureMatching)
+                whenever(
+                    checkCapability(any<SigningDetails>(), any())
+                ).thenReturn(isSignatureMatching)
+            }
+            whenever(this.signingDetails).thenReturn(signingDetails)
+        }
+
+    protected fun mockParsedPermission(
+        permissionName: String,
+        packageName: String,
+        backgroundPermission: String? = null,
+        group: String? = null,
+        protectionLevel: Int = PermissionInfo.PROTECTION_NORMAL,
+        flags: Int = 0,
+        isTree: Boolean = false
+    ): ParsedPermission =
+        mock {
+            whenever(name).thenReturn(permissionName)
+            whenever(this.packageName).thenReturn(packageName)
+            whenever(metaData).thenReturn(Bundle())
+            whenever(this.backgroundPermission).thenReturn(backgroundPermission)
+            whenever(this.group).thenReturn(group)
+            whenever(this.protectionLevel).thenReturn(protectionLevel)
+            whenever(this.flags).thenReturn(flags)
+            whenever(this.isTree).thenReturn(isTree)
+        }
+
+    protected fun mockParsedPermissionGroup(
+        permissionGroupName: String,
+        packageName: String,
+    ): ParsedPermissionGroup =
+        mock {
+            whenever(name).thenReturn(permissionGroupName)
+            whenever(this.packageName).thenReturn(packageName)
+            whenever(metaData).thenReturn(Bundle())
+        }
+
+    protected fun addPackageState(
+        packageState: PackageState,
+        state: MutableAccessState = oldState
+    ) {
+        state.mutateExternalState().apply {
+            setPackageStates(
+                packageStates.toMutableMap().apply {
+                    put(packageState.packageName, packageState)
+                }
+            )
+            mutateAppIdPackageNames().mutateOrPut(packageState.appId) { MutableIndexedListSet() }
+                .add(packageState.packageName)
+        }
+    }
+
+    protected fun addDisabledSystemPackageState(
+        packageState: PackageState,
+        state: MutableAccessState = oldState
+    ) = state.mutateExternalState().apply {
+        (disabledSystemPackageStates as ArrayMap)[packageState.packageName] = packageState
+    }
+
+    protected fun addPermission(
+        parsedPermission: ParsedPermission,
+        type: Int = Permission.TYPE_MANIFEST,
+        isReconciled: Boolean = true,
+        state: MutableAccessState = oldState
+    ) {
+        val permissionInfo = PackageInfoUtils.generatePermissionInfo(
+            parsedPermission,
+            PackageManager.GET_META_DATA.toLong()
+        )!!
+        val appId = state.externalState.packageStates[permissionInfo.packageName]!!.appId
+        val permission = Permission(permissionInfo, isReconciled, type, appId)
+        if (parsedPermission.isTree) {
+            state.mutateSystemState().mutatePermissionTrees()[permission.name] = permission
+        } else {
+            state.mutateSystemState().mutatePermissions()[permission.name] = permission
+        }
+    }
+
+    protected fun addPermissionGroup(
+        parsedPermissionGroup: ParsedPermissionGroup,
+        state: MutableAccessState = oldState
+    ) {
+        state.mutateSystemState().mutatePermissionGroups()[parsedPermissionGroup.name] =
+            PackageInfoUtils.generatePermissionGroupInfo(
+                parsedPermissionGroup,
+                PackageManager.GET_META_DATA.toLong()
+            )!!
+    }
+
+    protected fun getPermission(
+        permissionName: String,
+        state: MutableAccessState = newState
+    ): Permission? = state.systemState.permissions[permissionName]
+
+    protected fun getPermissionTree(
+        permissionTreeName: String,
+        state: MutableAccessState = newState
+    ): Permission? = state.systemState.permissionTrees[permissionTreeName]
+
+    protected fun getPermissionGroup(
+        permissionGroupName: String,
+        state: MutableAccessState = newState
+    ): PermissionGroupInfo? = state.systemState.permissionGroups[permissionGroupName]
+
+    protected fun getPermissionFlags(
+        appId: Int,
+        userId: Int,
+        permissionName: String,
+        state: MutableAccessState = newState
+    ): Int =
+        state.userStates[userId]?.appIdPermissionFlags?.get(appId).getWithDefault(permissionName, 0)
+
+    protected fun setPermissionFlags(
+        appId: Int,
+        userId: Int,
+        permissionName: String,
+        flags: Int,
+        state: MutableAccessState = oldState
+    ) =
+        state.mutateUserState(userId)!!.mutateAppIdPermissionFlags().mutateOrPut(appId) {
+            MutableIndexedMap()
+        }.put(permissionName, flags)
+
+    companion object {
+        @JvmStatic protected val PACKAGE_NAME_0 = "packageName0"
+        @JvmStatic protected val PACKAGE_NAME_1 = "packageName1"
+        @JvmStatic protected val PACKAGE_NAME_2 = "packageName2"
+        @JvmStatic protected val MISSING_ANDROID_PACKAGE = "missingAndroidPackage"
+        @JvmStatic protected val PLATFORM_PACKAGE_NAME = "android"
+
+        @JvmStatic protected val APP_ID_0 = 0
+        @JvmStatic protected val APP_ID_1 = 1
+        @JvmStatic protected val PLATFORM_APP_ID = 2
+
+        @JvmStatic protected val PERMISSION_GROUP_NAME_0 = "permissionGroupName0"
+        @JvmStatic protected val PERMISSION_GROUP_NAME_1 = "permissionGroupName1"
+
+        @JvmStatic protected val PERMISSION_TREE_NAME = "permissionTree"
+
+        @JvmStatic protected val PERMISSION_NAME_0 = "permissionName0"
+        @JvmStatic protected val PERMISSION_NAME_1 = "permissionName1"
+        @JvmStatic protected val PERMISSION_NAME_2 = "permissionName2"
+        @JvmStatic protected val PERMISSION_READ_EXTERNAL_STORAGE =
+            Manifest.permission.READ_EXTERNAL_STORAGE
+        @JvmStatic protected val PERMISSION_POST_NOTIFICATIONS =
+            Manifest.permission.POST_NOTIFICATIONS
+        @JvmStatic protected val PERMISSION_BLUETOOTH_CONNECT =
+            Manifest.permission.BLUETOOTH_CONNECT
+        @JvmStatic protected val PERMISSION_ACCESS_BACKGROUND_LOCATION =
+            Manifest.permission.ACCESS_BACKGROUND_LOCATION
+        @JvmStatic protected val PERMISSION_ACCESS_MEDIA_LOCATION =
+            Manifest.permission.ACCESS_MEDIA_LOCATION
+
+        @JvmStatic protected val USER_ID_0 = 0
+        @JvmStatic protected val USER_ID_NEW = 1
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 349a597..430f600 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -687,7 +687,7 @@
         swipeAndHold(initCoords, edgeCoords);
 
         assertTrue(mMgh.mCurrentState == mMgh.mSinglePanningState);
-        assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_LEFT_EDGE);
+        assertTrue(mMgh.mOverscrollHandler.mOverscrollState == mMgh.OVERSCROLL_LEFT_EDGE);
         assertTrue(isZoomed());
     }
 
@@ -711,7 +711,7 @@
         swipeAndHold(initCoords, edgeCoords);
 
         assertTrue(mMgh.mCurrentState == mMgh.mSinglePanningState);
-        assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_RIGHT_EDGE);
+        assertTrue(mMgh.mOverscrollHandler.mOverscrollState == mMgh.OVERSCROLL_RIGHT_EDGE);
         assertTrue(isZoomed());
     }
 
@@ -734,7 +734,7 @@
 
         swipeAndHold(initCoords, edgeCoords);
 
-        assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_VERTICAL_EDGE);
+        assertTrue(mMgh.mOverscrollHandler.mOverscrollState == mMgh.OVERSCROLL_VERTICAL_EDGE);
         assertTrue(isZoomed());
     }
 
@@ -756,7 +756,7 @@
 
         swipeAndHold(initCoords, edgeCoords);
 
-        assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_NONE);
+        assertTrue(mMgh.mOverscrollHandler.mOverscrollState == mMgh.OVERSCROLL_NONE);
         assertTrue(isZoomed());
     }