Wiring into UserSwitcherActivity.

UserSwitcherActivity refactor: CL 7/7

This CL wires in the new modern architecture implementation into the
activity that hosts the user switcher experience. It does so behind a
feature flag and keeps the old implementation unchanged.

Some care has been taken to do minimal changes to the code to keep it
from changing too much as part of this CL. At the same time, we needed
to make some changes to avoid side-effects and work that this activity
would otherwise do (for example, the adapter was moved behind a "lazy"
so it doesn't get instantiated in the new implementation as
instantiating it registers it for updates with UserSwitcherController
which can later cause exceptions if the adapter isn't properly hooked
up).

Bug: 243844359
Test: extensive manual testing, see the following lines for more.
1. No-harm testing for the old implementation. With the flag off, made
   sure that all the behaviours detailed below work the same after this
   CL as they worked before.
2. New implementation matches old implementation. User journeys
   exercised:
   a. Starting from the lock-screen or after unlocking and with and
   without Settings > System > Multiple Users > "Add users from lock
   screen" set - made sure that the "Add" button only appears when
   allowed
   b. Orientation changes. Made sure that the activity keeps its state
   and there are no crashes.
   c. Selecting a different user. Made sure that the system switches to
   the other user and that our activity exits as expected.
   d. Turning off the screen causes the activity to exit.
   e. Touching the cancel button causes our activity to exit.
   f. Clicking Add > Guest properly switches into the guest user and
   exits the activity. Returning to the activity properly shows the
   guest use as the current user with the name "Exit Guest" and tapping
   it properly goes back to the main user. Also, the menu options behind
   the "add" button are properly restricted to just "add user", "add
   child user", and "manage users"
   g. Add user, add child user, and manage users menu options all
   navigate the user to the correct destinations

Change-Id: I874eff89a8a3b81d8d5a632837ce2ccad92c82a4
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index ff0f0d4..8a51cd6 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -27,6 +27,7 @@
 import android.os.Bundle
 import android.os.UserManager
 import android.provider.Settings
+import android.util.Log
 import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.View
@@ -37,6 +38,7 @@
 import android.widget.TextView
 import androidx.activity.ComponentActivity
 import androidx.constraintlayout.helper.widget.Flow
+import androidx.lifecycle.ViewModelProvider
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.util.UserIcons
 import com.android.settingslib.Utils
@@ -44,6 +46,8 @@
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
 import com.android.systemui.settings.UserTracker
@@ -52,6 +56,9 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA
 import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA
 import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.ui.binder.UserSwitcherViewBinder
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
+import dagger.Lazy
 import javax.inject.Inject
 import kotlin.math.ceil
 
@@ -63,11 +70,12 @@
 class UserSwitcherActivity @Inject constructor(
     private val userSwitcherController: UserSwitcherController,
     private val broadcastDispatcher: BroadcastDispatcher,
-    private val layoutInflater: LayoutInflater,
     private val falsingCollector: FalsingCollector,
     private val falsingManager: FalsingManager,
     private val userManager: UserManager,
-    private val userTracker: UserTracker
+    private val userTracker: UserTracker,
+    private val flags: FeatureFlags,
+    private val viewModelFactory: Lazy<UserSwitcherViewModel.Factory>,
 ) : ComponentActivity() {
 
     private lateinit var parent: UserSwitcherRootView
@@ -93,119 +101,31 @@
             false /* isAddSupervisedUser */
         )
 
-    private val adapter = object : BaseUserAdapter(userSwitcherController) {
-        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
-            val item = getItem(position)
-            var view = convertView as ViewGroup?
-            if (view == null) {
-                view = layoutInflater.inflate(
-                    R.layout.user_switcher_fullscreen_item,
-                    parent,
-                    false
-                ) as ViewGroup
-            }
-            (view.getChildAt(0) as ImageView).apply {
-                setImageDrawable(getDrawable(item))
-            }
-            (view.getChildAt(1) as TextView).apply {
-                setText(getName(getContext(), item))
-            }
-
-            view.setEnabled(item.isSwitchToEnabled)
-            view.setAlpha(
-                if (view.isEnabled()) {
-                    USER_SWITCH_ENABLED_ALPHA
-                } else {
-                    USER_SWITCH_DISABLED_ALPHA
-                }
-            )
-            view.setTag(USER_VIEW)
-            return view
-        }
-
-        override fun getName(context: Context, item: UserRecord): String {
-            return if (item == manageUserRecord) {
-                getString(R.string.manage_users)
-            } else {
-                super.getName(context, item)
-            }
-        }
-
-        fun findUserIcon(item: UserRecord): Drawable {
-            if (item == manageUserRecord) {
-                return getDrawable(R.drawable.ic_manage_users)
-            }
-            if (item.info == null) {
-                return getIconDrawable(this@UserSwitcherActivity, item)
-            }
-            val userIcon = userManager.getUserIcon(item.info.id)
-            if (userIcon != null) {
-                return BitmapDrawable(userIcon)
-            }
-            return UserIcons.getDefaultUserIcon(resources, item.info.id, false)
-        }
-
-        fun getTotalUserViews(): Int {
-            return users.count { item ->
-                !doNotRenderUserView(item)
-            }
-        }
-
-        fun doNotRenderUserView(item: UserRecord): Boolean {
-            return item.isAddUser ||
-                    item.isAddSupervisedUser ||
-                    item.isGuest && item.info == null
-        }
-
-        private fun getDrawable(item: UserRecord): Drawable {
-            var drawable = if (item.isGuest) {
-                getDrawable(R.drawable.ic_account_circle)
-            } else {
-                findUserIcon(item)
-            }
-            drawable.mutate()
-
-            if (!item.isCurrent && !item.isSwitchToEnabled) {
-                drawable.setTint(
-                    resources.getColor(
-                        R.color.kg_user_switcher_restricted_avatar_icon_color,
-                        getTheme()
-                    )
-                )
-            }
-
-            val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate()
-                as LayerDrawable
-            if (item == userSwitcherController.getCurrentUserRecord()) {
-                (ld.findDrawableByLayerId(R.id.ring) as GradientDrawable).apply {
-                    val stroke = resources
-                        .getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width)
-                    val color = Utils.getColorAttrDefaultColor(
-                        this@UserSwitcherActivity,
-                        com.android.internal.R.attr.colorAccentPrimary
-                    )
-
-                    setStroke(stroke, color)
-                }
-            }
-
-            ld.setDrawableByLayerId(R.id.user_avatar, drawable)
-            return ld
-        }
-
-        override fun notifyDataSetChanged() {
-            super.notifyDataSetChanged()
-            buildUserViews()
-        }
-    }
+    private val adapter: UserAdapter by lazy { UserAdapter() }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
         setContentView(R.layout.user_switcher_fullscreen)
-        window.decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-            or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-            or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
+        window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
+        if (isUsingModernArchitecture()) {
+            Log.d(TAG, "Using modern architecture.")
+            val viewModel = ViewModelProvider(
+                this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
+            UserSwitcherViewBinder.bind(
+                view = requireViewById(R.id.user_switcher_root),
+                viewModel = viewModel,
+                lifecycleOwner = this,
+                layoutInflater = layoutInflater,
+                falsingCollector = falsingCollector,
+                onFinish = this::finish,
+            )
+            return
+        } else {
+            Log.d(TAG, "Not using modern architecture.")
+        }
 
         parent = requireViewById<UserSwitcherRootView>(R.id.user_switcher_root)
 
@@ -346,11 +266,18 @@
     }
 
     override fun onBackPressed() {
+        if (isUsingModernArchitecture()) {
+            return super.onBackPressed()
+        }
+
         finish()
     }
 
     override fun onDestroy() {
         super.onDestroy()
+        if (isUsingModernArchitecture()) {
+            return
+        }
 
         broadcastDispatcher.unregisterReceiver(broadcastReceiver)
         userTracker.removeCallback(userSwitchedCallback)
@@ -376,6 +303,10 @@
         return if (userCount < 5) 4 else ceil(userCount / 2.0).toInt()
     }
 
+    private fun isUsingModernArchitecture(): Boolean {
+        return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY)
+    }
+
     private class ItemAdapter(
         val parentContext: Context,
         val resource: Int,
@@ -398,4 +329,114 @@
             return view
         }
     }
+
+    private inner class UserAdapter : BaseUserAdapter(userSwitcherController) {
+        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+            val item = getItem(position)
+            var view = convertView as ViewGroup?
+            if (view == null) {
+                view = layoutInflater.inflate(
+                    R.layout.user_switcher_fullscreen_item,
+                    parent,
+                    false
+                ) as ViewGroup
+            }
+            (view.getChildAt(0) as ImageView).apply {
+                setImageDrawable(getDrawable(item))
+            }
+            (view.getChildAt(1) as TextView).apply {
+                setText(getName(getContext(), item))
+            }
+
+            view.setEnabled(item.isSwitchToEnabled)
+            view.setAlpha(
+                if (view.isEnabled()) {
+                    USER_SWITCH_ENABLED_ALPHA
+                } else {
+                    USER_SWITCH_DISABLED_ALPHA
+                }
+            )
+            view.setTag(USER_VIEW)
+            return view
+        }
+
+        override fun getName(context: Context, item: UserRecord): String {
+            return if (item == manageUserRecord) {
+                getString(R.string.manage_users)
+            } else {
+                super.getName(context, item)
+            }
+        }
+
+        fun findUserIcon(item: UserRecord): Drawable {
+            if (item == manageUserRecord) {
+                return getDrawable(R.drawable.ic_manage_users)
+            }
+            if (item.info == null) {
+                return getIconDrawable(this@UserSwitcherActivity, item)
+            }
+            val userIcon = userManager.getUserIcon(item.info.id)
+            if (userIcon != null) {
+                return BitmapDrawable(userIcon)
+            }
+            return UserIcons.getDefaultUserIcon(resources, item.info.id, false)
+        }
+
+        fun getTotalUserViews(): Int {
+            return users.count { item ->
+                !doNotRenderUserView(item)
+            }
+        }
+
+        fun doNotRenderUserView(item: UserRecord): Boolean {
+            return item.isAddUser ||
+                    item.isAddSupervisedUser ||
+                    item.isGuest && item.info == null
+        }
+
+        private fun getDrawable(item: UserRecord): Drawable {
+            var drawable = if (item.isGuest) {
+                getDrawable(R.drawable.ic_account_circle)
+            } else {
+                findUserIcon(item)
+            }
+            drawable.mutate()
+
+            if (!item.isCurrent && !item.isSwitchToEnabled) {
+                drawable.setTint(
+                    resources.getColor(
+                        R.color.kg_user_switcher_restricted_avatar_icon_color,
+                        getTheme()
+                    )
+                )
+            }
+
+            val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate()
+                    as LayerDrawable
+            if (item == userSwitcherController.getCurrentUserRecord()) {
+                (ld.findDrawableByLayerId(R.id.ring) as GradientDrawable).apply {
+                    val stroke = resources
+                        .getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width)
+                    val color = Utils.getColorAttrDefaultColor(
+                        this@UserSwitcherActivity,
+                        com.android.internal.R.attr.colorAccentPrimary
+                    )
+
+                    setStroke(stroke, color)
+                }
+            }
+
+            ld.setDrawableByLayerId(R.id.user_avatar, drawable)
+            return ld
+        }
+
+        override fun notifyDataSetChanged() {
+            super.notifyDataSetChanged()
+            buildUserViews()
+        }
+    }
+
+    companion object {
+        private const val TAG = "UserSwitcherActivity"
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
index 66367ec..439beaa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
@@ -24,9 +24,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -54,6 +56,10 @@
     private lateinit var userManager: UserManager
     @Mock
     private lateinit var userTracker: UserTracker
+    @Mock
+    private lateinit var flags: FeatureFlags
+    @Mock
+    private lateinit var viewModelFactoryLazy: dagger.Lazy<UserSwitcherViewModel.Factory>
 
     @Before
     fun setUp() {
@@ -61,11 +67,12 @@
         activity = UserSwitcherActivity(
             userSwitcherController,
             broadcastDispatcher,
-            layoutInflater,
             falsingCollector,
             falsingManager,
             userManager,
-            userTracker
+            userTracker,
+            flags,
+            viewModelFactoryLazy,
         )
     }