Merge "Allow UI update upon auth/action entry changes."
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 0802afe..5136f04 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -27,6 +27,7 @@
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.compose.setContent
 import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
 import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -43,17 +44,6 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity")
-        init(intent)
-    }
-
-    override fun onNewIntent(intent: Intent) {
-        super.onNewIntent(intent)
-        setIntent(intent)
-        Log.d(Constants.LOG_TAG, "Existing activity received new intent")
-        init(intent)
-    }
-
-    fun init(intent: Intent) {
         try {
             val userConfigRepo = UserConfigRepo(this)
             val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
@@ -70,6 +60,20 @@
         }
     }
 
+    override fun onNewIntent(intent: Intent) {
+        super.onNewIntent(intent)
+        setIntent(intent)
+        Log.d(Constants.LOG_TAG, "Existing activity received new intent")
+        try {
+            val userConfigRepo = UserConfigRepo(this)
+            val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
+            val viewModel: CredentialSelectorViewModel by viewModels()
+            viewModel.onNewCredentialManagerRepo(credManRepo)
+        } catch (e: Exception) {
+            onInitializationError(e, intent)
+        }
+    }
+
     @ExperimentalMaterialApi
     @Composable
     fun CredentialManagerBottomSheet(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 47ea53b..6bf1513 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -104,7 +104,9 @@
                     entry.providerId, entry.entryKey, entry.entrySubkey,
                     resultCode, resultData,
                 )
-                uiState = uiState.copy(dialogState = DialogState.COMPLETE)
+                if (entry.shouldTerminateUiUponSuccessfulProviderResult) {
+                    uiState = uiState.copy(dialogState = DialogState.COMPLETE)
+                }
             } else {
                 Log.w(Constants.LOG_TAG,
                     "Illegal state: received a provider result but found no matching entry.")
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
index 4b8bc97..ee36989 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
@@ -25,4 +25,5 @@
     val entrySubkey: String,
     val pendingIntent: PendingIntent?,
     val fillInIntent: Intent?,
+    val shouldTerminateUiUponSuccessfulProviderResult: Boolean,
 )
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 919411e..8d20564 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -69,7 +69,14 @@
     val totalCredentialCount: Int?,
     val lastUsedTime: Instant?,
     val footerDescription: String?,
-) : BaseEntry(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+) : BaseEntry(
+    providerId,
+    entryKey,
+    entrySubkey,
+    pendingIntent,
+    fillInIntent,
+    shouldTerminateUiUponSuccessfulProviderResult = true,
+)
 
 class RemoteInfo(
   providerId: String,
@@ -77,7 +84,14 @@
   entrySubkey: String,
   pendingIntent: PendingIntent?,
   fillInIntent: Intent?,
-) : BaseEntry(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+) : BaseEntry(
+    providerId,
+    entryKey,
+    entrySubkey,
+    pendingIntent,
+    fillInIntent,
+    shouldTerminateUiUponSuccessfulProviderResult = true,
+)
 
 data class RequestDisplayInfo(
   val title: String,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 5ab933a..bca06c7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -35,78 +35,105 @@
 )
 
 data class ProviderInfo(
-  /**
-   * Unique id (component name) of this provider.
-   * Not for display purpose - [displayName] should be used for ui rendering.
-   */
-  val id: String,
-  val icon: Drawable,
-  val displayName: String,
-  val credentialEntryList: List<CredentialEntryInfo>,
-  val authenticationEntryList: List<AuthenticationEntryInfo>,
-  val remoteEntry: RemoteEntryInfo?,
-  val actionEntryList: List<ActionEntryInfo>,
+    /**
+     * Unique id (component name) of this provider.
+     * Not for display purpose - [displayName] should be used for ui rendering.
+     */
+    val id: String,
+    val icon: Drawable,
+    val displayName: String,
+    val credentialEntryList: List<CredentialEntryInfo>,
+    val authenticationEntryList: List<AuthenticationEntryInfo>,
+    val remoteEntry: RemoteEntryInfo?,
+    val actionEntryList: List<ActionEntryInfo>,
 )
 
 /** Display-centric data structure derived from the [ProviderInfo]. This abstraction is not grouping
  *  by the provider id but instead focuses on structures convenient for display purposes. */
 data class ProviderDisplayInfo(
-  /**
-   * The credential entries grouped by userName, derived from all entries of the [providerInfoList].
-   * Note that the list order matters to the display order.
-   */
-  val sortedUserNameToCredentialEntryList: List<PerUserNameCredentialEntryList>,
-  val authenticationEntryList: List<AuthenticationEntryInfo>,
-  val remoteEntry: RemoteEntryInfo?
+    /**
+     * The credential entries grouped by userName, derived from all entries of the [providerInfoList].
+     * Note that the list order matters to the display order.
+     */
+    val sortedUserNameToCredentialEntryList: List<PerUserNameCredentialEntryList>,
+    val authenticationEntryList: List<AuthenticationEntryInfo>,
+    val remoteEntry: RemoteEntryInfo?
 )
 
 class CredentialEntryInfo(
-  providerId: String,
-  entryKey: String,
-  entrySubkey: String,
-  pendingIntent: PendingIntent?,
-  fillInIntent: Intent?,
-  /** Type of this credential used for sorting. Not localized so must not be directly displayed. */
-  val credentialType: CredentialType,
-  /** Localized type value of this credential used for display purpose. */
-  val credentialTypeDisplayName: String,
-  val userName: String,
-  val displayName: String?,
-  val icon: Drawable?,
-  val lastUsedTimeMillis: Instant?,
-) : BaseEntry(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+    providerId: String,
+    entryKey: String,
+    entrySubkey: String,
+    pendingIntent: PendingIntent?,
+    fillInIntent: Intent?,
+    /** Type of this credential used for sorting. Not localized so must not be directly displayed. */
+    val credentialType: CredentialType,
+    /** Localized type value of this credential used for display purpose. */
+    val credentialTypeDisplayName: String,
+    val userName: String,
+    val displayName: String?,
+    val icon: Drawable?,
+    val lastUsedTimeMillis: Instant?,
+) : BaseEntry(
+    providerId,
+    entryKey,
+    entrySubkey,
+    pendingIntent,
+    fillInIntent,
+    shouldTerminateUiUponSuccessfulProviderResult = true,
+)
 
 class AuthenticationEntryInfo(
-  providerId: String,
-  entryKey: String,
-  entrySubkey: String,
-  pendingIntent: PendingIntent?,
-  fillInIntent: Intent?,
-  val title: String,
-  val icon: Drawable,
-) : BaseEntry(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+    providerId: String,
+    entryKey: String,
+    entrySubkey: String,
+    pendingIntent: PendingIntent?,
+    fillInIntent: Intent?,
+    val title: String,
+    val icon: Drawable,
+) : BaseEntry(
+    providerId,
+    entryKey, entrySubkey,
+    pendingIntent,
+    fillInIntent,
+    shouldTerminateUiUponSuccessfulProviderResult = false,
+)
 
 class RemoteEntryInfo(
-  providerId: String,
-  entryKey: String,
-  entrySubkey: String,
-  pendingIntent: PendingIntent?,
-  fillInIntent: Intent?,
-) : BaseEntry(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+    providerId: String,
+    entryKey: String,
+    entrySubkey: String,
+    pendingIntent: PendingIntent?,
+    fillInIntent: Intent?,
+) : BaseEntry(
+    providerId,
+    entryKey,
+    entrySubkey,
+    pendingIntent,
+    fillInIntent,
+    shouldTerminateUiUponSuccessfulProviderResult = true,
+)
 
 class ActionEntryInfo(
-  providerId: String,
-  entryKey: String,
-  entrySubkey: String,
-  pendingIntent: PendingIntent?,
-  fillInIntent: Intent?,
-  val title: String,
-  val icon: Drawable,
-  val subTitle: String?,
-) : BaseEntry(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+    providerId: String,
+    entryKey: String,
+    entrySubkey: String,
+    pendingIntent: PendingIntent?,
+    fillInIntent: Intent?,
+    val title: String,
+    val icon: Drawable,
+    val subTitle: String?,
+) : BaseEntry(
+    providerId,
+    entryKey,
+    entrySubkey,
+    pendingIntent,
+    fillInIntent,
+    shouldTerminateUiUponSuccessfulProviderResult = false,
+)
 
 data class RequestDisplayInfo(
-  val appName: String,
+    val appName: String,
 )
 
 /**
@@ -115,18 +142,20 @@
  *                                     by last used timestamps and then by credential types
  */
 data class PerUserNameCredentialEntryList(
-  val userName: String,
-  val sortedCredentialEntryList: List<CredentialEntryInfo>,
+    val userName: String,
+    val sortedCredentialEntryList: List<CredentialEntryInfo>,
 )
 
 /** The name of the current screen. */
 enum class GetScreenState {
-  /** The primary credential selection page. */
-  PRIMARY_SELECTION,
-  /** The secondary credential selection page, where all sign-in options are listed. */
-  ALL_SIGN_IN_OPTIONS,
-  /** The snackbar only page when there's no account but only a remoteEntry. */
-  REMOTE_ONLY,
+    /** The primary credential selection page. */
+    PRIMARY_SELECTION,
+
+    /** The secondary credential selection page, where all sign-in options are listed. */
+    ALL_SIGN_IN_OPTIONS,
+
+    /** The snackbar only page when there's no account but only a remoteEntry. */
+    REMOTE_ONLY,
 }
 
 // IMPORTANT: new invocation should be mindful that this method will throw if more than 1 remote
@@ -135,113 +164,113 @@
     providerInfoList: List<ProviderInfo>
 ): ProviderDisplayInfo {
 
-  val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>()
-  val authenticationEntryList = mutableListOf<AuthenticationEntryInfo>()
-  val remoteEntryList = mutableListOf<RemoteEntryInfo>()
-  providerInfoList.forEach { providerInfo ->
-    authenticationEntryList.addAll(providerInfo.authenticationEntryList)
-    if (providerInfo.remoteEntry != null) {
-      remoteEntryList.add(providerInfo.remoteEntry)
-    }
-    // There can only be at most one remote entry
-    Preconditions.checkState(remoteEntryList.size <= 1)
-
-    providerInfo.credentialEntryList.forEach {
-      userNameToCredentialEntryMap.compute(
-          it.userName
-      ) { _, v ->
-        if (v == null) {
-          mutableListOf(it)
-        } else {
-          v.add(it)
-          v
+    val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>()
+    val authenticationEntryList = mutableListOf<AuthenticationEntryInfo>()
+    val remoteEntryList = mutableListOf<RemoteEntryInfo>()
+    providerInfoList.forEach { providerInfo ->
+        authenticationEntryList.addAll(providerInfo.authenticationEntryList)
+        if (providerInfo.remoteEntry != null) {
+            remoteEntryList.add(providerInfo.remoteEntry)
         }
-      }
+        // There can only be at most one remote entry
+        Preconditions.checkState(remoteEntryList.size <= 1)
+
+        providerInfo.credentialEntryList.forEach {
+            userNameToCredentialEntryMap.compute(
+                it.userName
+            ) { _, v ->
+                if (v == null) {
+                    mutableListOf(it)
+                } else {
+                    v.add(it)
+                    v
+                }
+            }
+        }
     }
-  }
 
-  // Compose sortedUserNameToCredentialEntryList
-  val comparator = CredentialEntryInfoComparatorByTypeThenTimestamp()
-  // Sort per username
-  userNameToCredentialEntryMap.values.forEach {
-    it.sortWith(comparator)
-  }
-  // Transform to list of PerUserNameCredentialEntryLists and then sort across usernames
-  val sortedUserNameToCredentialEntryList = userNameToCredentialEntryMap.map {
-    PerUserNameCredentialEntryList(it.key, it.value)
-  }.sortedWith(
-      compareByDescending { it.sortedCredentialEntryList.first().lastUsedTimeMillis }
-  )
+    // Compose sortedUserNameToCredentialEntryList
+    val comparator = CredentialEntryInfoComparatorByTypeThenTimestamp()
+    // Sort per username
+    userNameToCredentialEntryMap.values.forEach {
+        it.sortWith(comparator)
+    }
+    // Transform to list of PerUserNameCredentialEntryLists and then sort across usernames
+    val sortedUserNameToCredentialEntryList = userNameToCredentialEntryMap.map {
+        PerUserNameCredentialEntryList(it.key, it.value)
+    }.sortedWith(
+        compareByDescending { it.sortedCredentialEntryList.first().lastUsedTimeMillis }
+    )
 
-  return ProviderDisplayInfo(
-      sortedUserNameToCredentialEntryList = sortedUserNameToCredentialEntryList,
-      authenticationEntryList = authenticationEntryList,
-      remoteEntry = remoteEntryList.getOrNull(0),
-  )
+    return ProviderDisplayInfo(
+        sortedUserNameToCredentialEntryList = sortedUserNameToCredentialEntryList,
+        authenticationEntryList = authenticationEntryList,
+        remoteEntry = remoteEntryList.getOrNull(0),
+    )
 }
 
 private fun toActiveEntry(
     providerDisplayInfo: ProviderDisplayInfo,
 ): BaseEntry? {
-  val sortedUserNameToCredentialEntryList =
-      providerDisplayInfo.sortedUserNameToCredentialEntryList
-  val authenticationEntryList = providerDisplayInfo.authenticationEntryList
-  var activeEntry: BaseEntry? = null
-  if (sortedUserNameToCredentialEntryList
-          .size == 1 && authenticationEntryList.isEmpty()
-  ) {
-    activeEntry = sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList.first()
-  } else if (
-      sortedUserNameToCredentialEntryList
-          .isEmpty() && authenticationEntryList.size == 1
-  ) {
-    activeEntry = authenticationEntryList.first()
-  }
-  return activeEntry
+    val sortedUserNameToCredentialEntryList =
+        providerDisplayInfo.sortedUserNameToCredentialEntryList
+    val authenticationEntryList = providerDisplayInfo.authenticationEntryList
+    var activeEntry: BaseEntry? = null
+    if (sortedUserNameToCredentialEntryList
+            .size == 1 && authenticationEntryList.isEmpty()
+    ) {
+        activeEntry = sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList.first()
+    } else if (
+        sortedUserNameToCredentialEntryList
+            .isEmpty() && authenticationEntryList.size == 1
+    ) {
+        activeEntry = authenticationEntryList.first()
+    }
+    return activeEntry
 }
 
 private fun toGetScreenState(
     providerInfoList: List<ProviderInfo>
 ): GetScreenState {
-  var noLocalAccount = true
-  var remoteInfo: RemoteEntryInfo? = null
-  providerInfoList.forEach { providerInfo ->
-    if (providerInfo.credentialEntryList.isNotEmpty() ||
-        providerInfo.authenticationEntryList.isNotEmpty()) {
-      noLocalAccount = false
+    var noLocalAccount = true
+    var remoteInfo: RemoteEntryInfo? = null
+    providerInfoList.forEach { providerInfo ->
+        if (providerInfo.credentialEntryList.isNotEmpty() ||
+            providerInfo.authenticationEntryList.isNotEmpty()) {
+            noLocalAccount = false
+        }
+        if (providerInfo.remoteEntry != null) {
+            remoteInfo = providerInfo.remoteEntry
+        }
     }
-    if (providerInfo.remoteEntry != null) {
-      remoteInfo = providerInfo.remoteEntry
-    }
-  }
 
-  return if (noLocalAccount && remoteInfo != null)
-    GetScreenState.REMOTE_ONLY else GetScreenState.PRIMARY_SELECTION
+    return if (noLocalAccount && remoteInfo != null)
+        GetScreenState.REMOTE_ONLY else GetScreenState.PRIMARY_SELECTION
 }
 
 internal class CredentialEntryInfoComparatorByTypeThenTimestamp : Comparator<CredentialEntryInfo> {
-  override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
-    // First prefer passkey type for its security benefits
-    if (p0.credentialType != p1.credentialType) {
-      if (CredentialType.PASSKEY == p0.credentialType) {
-        return -1
-      } else if (CredentialType.PASSKEY == p1.credentialType) {
-        return 1
-      }
-    }
+    override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
+        // First prefer passkey type for its security benefits
+        if (p0.credentialType != p1.credentialType) {
+            if (CredentialType.PASSKEY == p0.credentialType) {
+                return -1
+            } else if (CredentialType.PASSKEY == p1.credentialType) {
+                return 1
+            }
+        }
 
-    // Then order by last used timestamp
-    if (p0.lastUsedTimeMillis != null && p1.lastUsedTimeMillis != null) {
-      if (p0.lastUsedTimeMillis < p1.lastUsedTimeMillis) {
-        return 1
-      } else if (p0.lastUsedTimeMillis > p1.lastUsedTimeMillis) {
-        return -1
-      }
-    } else if (p0.lastUsedTimeMillis != null) {
-      return -1
-    } else if (p1.lastUsedTimeMillis != null) {
-      return 1
+        // Then order by last used timestamp
+        if (p0.lastUsedTimeMillis != null && p1.lastUsedTimeMillis != null) {
+            if (p0.lastUsedTimeMillis < p1.lastUsedTimeMillis) {
+                return 1
+            } else if (p0.lastUsedTimeMillis > p1.lastUsedTimeMillis) {
+                return -1
+            }
+        } else if (p0.lastUsedTimeMillis != null) {
+            return -1
+        } else if (p1.lastUsedTimeMillis != null) {
+            return 1
+        }
+        return 0
     }
-    return 0
-  }
 }
\ No newline at end of file