Merge "Use abstractions to allow FullscreenUserSwitcher take advantage of SystemUIOverlayWindow." into rvc-dev
diff --git a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
index 2fe9d21..55207b3 100644
--- a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
+++ b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
@@ -16,6 +16,7 @@
 -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/fullscreen_user_switcher"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
@@ -24,22 +25,26 @@
         android:id="@+id/container"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:layout_alignParentTop="true"
         android:orientation="vertical">
 
-        <include
-            layout="@layout/car_status_bar_header"
-            android:layout_alignParentTop="true"
-            android:theme="@android:style/Theme"/>
+        <!-- TODO(b/150302361): Status bar is commented out since a top inset is being added which causes it to be displayed below the top of the screen. -->
+        <!--        <include
+                    layout="@layout/car_status_bar_header"
+                    android:layout_alignParentTop="true"
+                    android:theme="@android:style/Theme"/>-->
+
 
         <FrameLayout
             android:layout_width="match_parent"
             android:layout_height="match_parent">
-            <com.android.systemui.statusbar.car.UserGridRecyclerView
+            <com.android.systemui.car.userswitcher.UserGridRecyclerView
                 android:id="@+id/user_grid"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical"
-                android:layout_marginTop="@dimen/car_user_switcher_margin_top"/>
+                android:layout_gravity="center_vertical"/>
+            <!-- TODO(b/150302361): Re-add marginTop once status bar has been added back. -->
+            <!--                android:layout_marginTop="@dimen/car_user_switcher_margin_top"/>-->
         </FrameLayout>
 
     </LinearLayout>
diff --git a/packages/CarSystemUI/res/layout/car_qs_panel.xml b/packages/CarSystemUI/res/layout/car_qs_panel.xml
index 9c598d7..0c6f322 100644
--- a/packages/CarSystemUI/res/layout/car_qs_panel.xml
+++ b/packages/CarSystemUI/res/layout/car_qs_panel.xml
@@ -33,7 +33,7 @@
         android:layout_width="match_parent"
         android:layout_height="@dimen/car_user_switcher_container_height">
 
-        <com.android.systemui.statusbar.car.UserGridRecyclerView
+        <com.android.systemui.car.userswitcher.UserGridRecyclerView
             android:id="@+id/user_grid"
             android:layout_width="match_parent"
             android:layout_height="match_parent"/>
diff --git a/packages/CarSystemUI/res/layout/sysui_primary_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
similarity index 100%
rename from packages/CarSystemUI/res/layout/sysui_primary_window.xml
rename to packages/CarSystemUI/res/layout/sysui_overlay_window.xml
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index 825b281..aaa65de 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -58,6 +58,11 @@
         to a constant alpha percent value using the initial alpha. -->
     <integer name="config_finalNotificationBackgroundAlpha">100</integer>
 
+    <!-- Car System UI's OverlayViewsMediator-->
+    <string-array name="config_carSystemUIOverlayViewsMediators" translatable="false">
+        <item>com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator</item>
+    </string-array>
+
     <!-- SystemUI Services: The classes of the stuff to start. -->
     <string-array name="config_systemUIServiceComponents" translatable="false">
         <item>com.android.systemui.util.NotificationChannels</item>
@@ -85,5 +90,6 @@
         <item>com.android.systemui.navigationbar.car.CarNavigationBar</item>
         <item>com.android.systemui.toast.ToastUI</item>
         <item>com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier</item>
+        <item>com.android.systemui.window.SystemUIOverlayWindowManager</item>
     </string-array>
 </resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
index 8f9d7ed..c010881 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
@@ -39,6 +39,8 @@
 import com.android.systemui.util.leak.GarbageMonitor;
 import com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier;
 import com.android.systemui.volume.VolumeUI;
+import com.android.systemui.window.OverlayWindowModule;
+import com.android.systemui.window.SystemUIOverlayWindowManager;
 
 import dagger.Binds;
 import dagger.Module;
@@ -47,7 +49,7 @@
 
 /** Binder for car specific {@link SystemUI} modules. */
 @Module(includes = {RecentsModule.class, CarStatusBarModule.class, NotificationsModule.class,
-        BubbleModule.class, KeyguardModule.class})
+        BubbleModule.class, KeyguardModule.class, OverlayWindowModule.class})
 public abstract class CarSystemUIBinder {
     /** Inject into AuthController. */
     @Binds
@@ -182,4 +184,10 @@
     @ClassKey(ConnectedDeviceVoiceRecognitionNotifier.class)
     public abstract SystemUI bindConnectedDeviceVoiceRecognitionNotifier(
             ConnectedDeviceVoiceRecognitionNotifier sysui);
+
+    /** Inject into SystemUIOverlayWindowManager. */
+    @Binds
+    @IntoMap
+    @ClassKey(SystemUIOverlayWindowManager.class)
+    public abstract SystemUI bindSystemUIPrimaryWindowManager(SystemUIOverlayWindowManager sysui);
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/CarTrustAgentUnlockDialogHelper.java
similarity index 98%
rename from packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
rename to packages/CarSystemUI/src/com/android/systemui/car/userswitcher/CarTrustAgentUnlockDialogHelper.java
index 07dbd66..5977165 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/CarTrustAgentUnlockDialogHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.car;
+package com.android.systemui.car.userswitcher;
 
 import android.app.admin.DevicePolicyManager;
 import android.bluetooth.BluetoothAdapter;
@@ -56,8 +56,8 @@
     private final UserManager mUserManager;
     private final WindowManager.LayoutParams mParams;
     /**
-     * Not using Dialog because context passed from {@link FullscreenUserSwitcher} is not an
-     * activity.
+     * Not using Dialog because context passed from {@link FullscreenUserSwitcherViewMediator}
+     * is not an activity.
      */
     private final View mUnlockDialogLayout;
     private final TextView mUnlockingText;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
new file mode 100644
index 0000000..45ceb6d
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 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.systemui.car.userswitcher;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.View;
+
+import androidx.recyclerview.widget.GridLayoutManager;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.window.OverlayViewController;
+import com.android.systemui.window.OverlayViewGlobalStateController;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Controller for {@link R.layout#car_fullscreen_user_switcher}.
+ */
+@Singleton
+public class FullScreenUserSwitcherViewController extends OverlayViewController {
+    private final Context mContext;
+    private final Resources mResources;
+    private final int mShortAnimationDuration;
+    private UserGridRecyclerView mUserGridView;
+    private UserGridRecyclerView.UserSelectionListener mUserSelectionListener;
+
+    @Inject
+    public FullScreenUserSwitcherViewController(
+            Context context,
+            @Main Resources resources,
+            OverlayViewGlobalStateController overlayViewGlobalStateController) {
+        super(R.id.fullscreen_user_switcher_stub, overlayViewGlobalStateController);
+        mContext = context;
+        mResources = resources;
+        mShortAnimationDuration = mResources.getInteger(android.R.integer.config_shortAnimTime);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        // Initialize user grid.
+        mUserGridView = getLayout().findViewById(R.id.user_grid);
+        GridLayoutManager layoutManager = new GridLayoutManager(mContext,
+                mResources.getInteger(R.integer.user_fullscreen_switcher_num_col));
+        mUserGridView.setLayoutManager(layoutManager);
+        mUserGridView.buildAdapter();
+        mUserGridView.setUserSelectionListener(mUserSelectionListener);
+    }
+
+    @Override
+    protected void showInternal() {
+        getLayout().setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    protected void hideInternal() {
+        // Switching is about to happen, since it takes time, fade out the switcher gradually.
+        fadeOut();
+    }
+
+    private void fadeOut() {
+        mUserGridView.animate()
+                .alpha(0.0f)
+                .setDuration(mShortAnimationDuration)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        getLayout().setVisibility(View.GONE);
+                        mUserGridView.setAlpha(1.0f);
+                    }
+                });
+
+    }
+
+    /**
+     * Invalidate underlying view.
+     */
+    void invalidate() {
+        if (getLayout() == null) {
+            // layout hasn't been inflated.
+            return;
+        }
+
+        getLayout().invalidate();
+    }
+
+    /**
+     * Set {@link UserGridRecyclerView.UserSelectionListener}.
+     */
+    void setUserGridSelectionListener(
+            UserGridRecyclerView.UserSelectionListener userGridSelectionListener) {
+        mUserSelectionListener = userGridSelectionListener;
+    }
+
+    /**
+     * Returns {@code true} when layout is visible.
+     */
+    boolean isVisible() {
+        if (getLayout() == null) {
+            // layout hasn't been inflated.
+            return false;
+        }
+
+        return getLayout().getVisibility() == View.VISIBLE;
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
new file mode 100644
index 0000000..6277297
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2020 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.systemui.car.userswitcher;
+
+import android.car.Car;
+import android.car.trust.CarTrustAgentEnrollmentManager;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.R;
+import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.car.CarStatusBar;
+import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
+import com.android.systemui.window.OverlayViewMediator;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Manages the fullscreen user switcher and it's interactions with the keyguard.
+ */
+@Singleton
+public class FullscreenUserSwitcherViewMediator implements OverlayViewMediator {
+    private static final String TAG = FullscreenUserSwitcherViewMediator.class.getSimpleName();
+
+    private final Context mContext;
+    private final UserManager mUserManager;
+    private final CarServiceProvider mCarServiceProvider;
+    private final CarTrustAgentUnlockDialogHelper mUnlockDialogHelper;
+    private final CarStatusBarKeyguardViewManager mCarStatusBarKeyguardViewManager;
+    private final Handler mMainHandler;
+    private final StatusBarStateController mStatusBarStateController;
+    private final FullScreenUserSwitcherViewController mFullScreenUserSwitcherViewController;
+    private final ScreenLifecycle mScreenLifecycle;
+    private final CarStatusBar mCarStatusBar;
+    private final boolean mIsUserSwitcherEnabled;
+    private final CarUserManagerHelper mCarUserManagerHelper;
+
+    private CarTrustAgentEnrollmentManager mEnrollmentManager;
+    private UserGridRecyclerView.UserRecord mSelectedUser;
+    private final CarTrustAgentUnlockDialogHelper.OnHideListener mOnHideListener =
+            dismissUserSwitcher -> {
+                if (dismissUserSwitcher) {
+                    dismissUserSwitcher();
+                } else {
+                    // Re-draw the parent view, otherwise the unlock dialog will not be removed
+                    // from the screen immediately.
+                    invalidateFullscreenUserSwitcherView();
+                }
+            };
+    private final BroadcastReceiver mUserUnlockReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "user 0 is unlocked, SharedPreference is accessible.");
+            }
+            showDialogForInitialUser();
+            mContext.unregisterReceiver(mUserUnlockReceiver);
+        }
+    };
+
+    @Inject
+    public FullscreenUserSwitcherViewMediator(
+            Context context,
+            @Main Resources resources,
+            @Main Handler mainHandler,
+            UserManager userManager,
+            CarServiceProvider carServiceProvider,
+            CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper,
+            CarStatusBarKeyguardViewManager carStatusBarKeyguardViewManager,
+            CarStatusBar carStatusBar,
+            StatusBarStateController statusBarStateController,
+            FullScreenUserSwitcherViewController fullScreenUserSwitcherViewController,
+            ScreenLifecycle screenLifecycle) {
+        mContext = context;
+
+        mIsUserSwitcherEnabled = resources.getBoolean(R.bool.config_enableFullscreenUserSwitcher);
+
+        mMainHandler = mainHandler;
+        mUserManager = userManager;
+
+        mCarServiceProvider = carServiceProvider;
+        mCarServiceProvider.addListener(
+                car -> mEnrollmentManager = (CarTrustAgentEnrollmentManager) car.getCarManager(
+                        Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE));
+
+        mUnlockDialogHelper = carTrustAgentUnlockDialogHelper;
+        mCarStatusBarKeyguardViewManager = carStatusBarKeyguardViewManager;
+        mCarStatusBar = carStatusBar;
+        mStatusBarStateController = statusBarStateController;
+        mFullScreenUserSwitcherViewController = fullScreenUserSwitcherViewController;
+        mScreenLifecycle = screenLifecycle;
+
+        mCarUserManagerHelper = new CarUserManagerHelper(mContext);
+    }
+
+    @Override
+    public void registerListeners() {
+        registerUserSwitcherShowListeners();
+        registerUserSwitcherHideListeners();
+        registerHideKeyguardListeners();
+
+        if (mUserManager.isUserUnlocked(UserHandle.USER_SYSTEM)) {
+            // User0 is unlocked, switched to the initial user
+            showDialogForInitialUser();
+        } else {
+            // listen to USER_UNLOCKED
+            mContext.registerReceiverAsUser(mUserUnlockReceiver,
+                    UserHandle.getUserHandleForUid(UserHandle.USER_SYSTEM),
+                    new IntentFilter(Intent.ACTION_USER_UNLOCKED),
+                    /* broadcastPermission= */ null,
+                    /* scheduler= */ null);
+        }
+    }
+
+    private void registerUserSwitcherShowListeners() {
+        mCarStatusBarKeyguardViewManager.addOnKeyguardCancelClickedListener(this::show);
+    }
+
+    private void registerUserSwitcherHideListeners() {
+        mStatusBarStateController.addCallback(new StatusBarStateController.StateListener() {
+            @Override
+            public void onStateChanged(int newState) {
+                if (newState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
+                    return;
+                }
+                hide();
+            }
+        });
+    }
+
+    private void registerHideKeyguardListeners() {
+        mStatusBarStateController.addCallback(new StatusBarStateController.StateListener() {
+            @Override
+            public void onStateChanged(int newState) {
+                if (newState != StatusBarState.FULLSCREEN_USER_SWITCHER) {
+                    return;
+                }
+                dismissKeyguardWhenUserSwitcherNotDisplayed(newState);
+            }
+        });
+
+        mScreenLifecycle.addObserver(new ScreenLifecycle.Observer() {
+            @Override
+            public void onScreenTurnedOn() {
+                dismissKeyguardWhenUserSwitcherNotDisplayed(mStatusBarStateController.getState());
+            }
+        });
+
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (!intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
+                    return;
+                }
+
+                // Try to dismiss the keyguard after every user switch.
+                dismissKeyguardWhenUserSwitcherNotDisplayed(mStatusBarStateController.getState());
+            }
+        }, new IntentFilter(Intent.ACTION_USER_SWITCHED));
+    }
+
+    @Override
+    public void setupOverlayContentViewControllers() {
+        mFullScreenUserSwitcherViewController.setUserGridSelectionListener(this::onUserSelected);
+    }
+
+    /**
+     * Every time user clicks on an item in the switcher, if the clicked user has no trusted
+     * device, we hide the switcher, either gradually or immediately.
+     * If the user has trusted device, we show an unlock dialog to notify user the unlock
+     * state.
+     * When the unlock dialog is dismissed by user, we hide the unlock dialog and the switcher.
+     * We dismiss the entire keyguard when we hide the switcher if user clicked on the
+     * foreground user (user we're already logged in as).
+     */
+    private void onUserSelected(UserGridRecyclerView.UserRecord record) {
+        mSelectedUser = record;
+        if (record.mInfo != null) {
+            if (hasScreenLock(record.mInfo.id) && hasTrustedDevice(record.mInfo.id)) {
+                mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, mOnHideListener);
+                return;
+            }
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "no trusted device enrolled for uid: " + record.mInfo.id);
+            }
+        }
+        dismissUserSwitcher();
+    }
+
+    // We automatically dismiss keyguard unless user switcher is being shown above the keyguard.
+    private void dismissKeyguardWhenUserSwitcherNotDisplayed(int state) {
+        if (!mIsUserSwitcherEnabled) {
+            return; // Not using the full screen user switcher.
+        }
+
+        if (state == StatusBarState.FULLSCREEN_USER_SWITCHER
+                && !mFullScreenUserSwitcherViewController.isVisible()) {
+            // Current execution path continues to set state after this, thus we deffer the
+            // dismissal to the next execution cycle.
+
+            // Dismiss the keyguard if switcher is not visible.
+            // TODO(b/150402329): Remove once keyguard is implemented using Overlay Window
+            //  abstractions.
+            mMainHandler.post(mCarStatusBar::dismissKeyguard);
+        }
+    }
+
+    private boolean hasScreenLock(int uid) {
+        LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
+        return lockPatternUtils.getCredentialTypeForUser(uid)
+                != LockPatternUtils.CREDENTIAL_TYPE_NONE;
+    }
+
+    private boolean hasTrustedDevice(int uid) {
+        if (mEnrollmentManager == null) { // car service not ready, so it cannot be available.
+            return false;
+        }
+        return !mEnrollmentManager.getEnrolledDeviceInfoForUser(uid).isEmpty();
+    }
+
+    private void dismissUserSwitcher() {
+        if (mSelectedUser == null) {
+            Log.e(TAG, "Request to dismiss user switcher, but no user selected");
+            return;
+        }
+        if (mSelectedUser.mType == UserGridRecyclerView.UserRecord.FOREGROUND_USER) {
+            hide();
+            mCarStatusBar.dismissKeyguard();
+            return;
+        }
+        hide();
+    }
+
+    private void showDialogForInitialUser() {
+        int initialUser = mCarUserManagerHelper.getInitialUser();
+        UserInfo initialUserInfo = mUserManager.getUserInfo(initialUser);
+        mSelectedUser = new UserGridRecyclerView.UserRecord(initialUserInfo,
+                UserGridRecyclerView.UserRecord.FOREGROUND_USER);
+
+        // If the initial user has screen lock and trusted device, display the unlock dialog on the
+        // keyguard.
+        if (hasScreenLock(initialUser) && hasTrustedDevice(initialUser)) {
+            mUnlockDialogHelper.showUnlockDialogAfterDelay(initialUser,
+                    mOnHideListener);
+        } else {
+            // If no trusted device, dismiss the keyguard.
+            dismissUserSwitcher();
+        }
+    }
+
+    private void invalidateFullscreenUserSwitcherView() {
+        mFullScreenUserSwitcherViewController.invalidate();
+    }
+
+    private void hide() {
+        mFullScreenUserSwitcherViewController.stop();
+    }
+
+    private void show() {
+        mFullScreenUserSwitcherViewController.start();
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
similarity index 97%
rename from packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
rename to packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
index 7dd3be4..58add17 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.car;
+package com.android.systemui.car.userswitcher;
 
 import static android.content.DialogInterface.BUTTON_NEGATIVE;
 import static android.content.DialogInterface.BUTTON_POSITIVE;
@@ -45,7 +45,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
-import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -114,8 +113,6 @@
 
     /**
      * Initializes the adapter that populates the grid layout
-     *
-     * @return the adapter
      */
     public void buildAdapter() {
         List<UserRecord> userRecords = createUserRecords(getUsersForUserGrid());
@@ -236,10 +233,16 @@
             mNewUserName = mRes.getString(R.string.car_new_user);
         }
 
+        /**
+         * Clears list of user records.
+         */
         public void clearUsers() {
             mUsers.clear();
         }
 
+        /**
+         * Updates list of user records.
+         */
         public void updateUsers(List<UserRecord> users) {
             mUsers = users;
         }
@@ -483,6 +486,10 @@
             return mUsers.size();
         }
 
+        /**
+         * An extension of {@link RecyclerView.ViewHolder} that also houses the user name and the
+         * user avatar.
+         */
         public class UserAdapterViewHolder extends RecyclerView.ViewHolder {
 
             public ImageView mUserAvatarImageView;
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserIconProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserIconProvider.java
similarity index 97%
rename from packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserIconProvider.java
rename to packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserIconProvider.java
index 9018290..dc5953e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserIconProvider.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserIconProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.car;
+package com.android.systemui.car.userswitcher;
 
 import android.annotation.UserIdInt;
 import android.content.Context;
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
index a56c4ed..67e9da4 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
@@ -71,6 +71,30 @@
         mShowRight = mContext.getResources().getBoolean(R.bool.config_enableRightNavigationBar);
     }
 
+    /**
+     * Hides all navigation bars.
+     */
+    public void hideBars() {
+        if (mTopView != null) {
+            mTopView.setVisibility(View.GONE);
+        }
+        setBottomWindowVisibility(View.GONE);
+        setLeftWindowVisibility(View.GONE);
+        setRightWindowVisibility(View.GONE);
+    }
+
+    /**
+     * Shows all navigation bars.
+     */
+    public void showBars() {
+        if (mTopView != null) {
+            mTopView.setVisibility(View.VISIBLE);
+        }
+        setBottomWindowVisibility(View.VISIBLE);
+        setLeftWindowVisibility(View.VISIBLE);
+        setRightWindowVisibility(View.VISIBLE);
+    }
+
     /** Connect to hvac service. */
     public void connectToHvac() {
         mHvacControllerLazy.get().connectToCarService();
diff --git a/packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFragment.java b/packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
index f9cfafa..31965c5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
+++ b/packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
@@ -35,9 +35,9 @@
 import androidx.recyclerview.widget.GridLayoutManager;
 
 import com.android.systemui.R;
+import com.android.systemui.car.userswitcher.UserGridRecyclerView;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.QSFooter;
-import com.android.systemui.statusbar.car.UserGridRecyclerView;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 0374a5c..83248f4 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -67,7 +67,6 @@
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.CarDeviceProvisionedListener;
 import com.android.systemui.car.CarServiceProvider;
-import com.android.systemui.car.SystemUIPrimaryWindowController;
 import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -95,7 +94,6 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -181,14 +179,13 @@
     private Drawable mNotificationPanelBackground;
 
     private final Object mQueueLock = new Object();
-    private final SystemUIPrimaryWindowController mSystemUIPrimaryWindowController;
     private final CarNavigationBarController mCarNavigationBarController;
     private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
     private final Lazy<PowerManagerHelper> mPowerManagerHelperLazy;
-    private final FullscreenUserSwitcher mFullscreenUserSwitcher;
     private final ShadeController mShadeController;
     private final CarServiceProvider mCarServiceProvider;
     private final CarDeviceProvisionedController mCarDeviceProvisionedController;
+    private final ScreenLifecycle mScreenLifecycle;
 
     private boolean mDeviceIsSetUpForUser = true;
     private boolean mIsUserSetupInProgress = false;
@@ -196,7 +193,6 @@
     private FlingAnimationUtils mFlingAnimationUtils;
     private NotificationDataManager mNotificationDataManager;
     private NotificationClickHandlerFactory mNotificationClickHandlerFactory;
-    private ScreenLifecycle mScreenLifecycle;
 
     // The container for the notifications.
     private CarNotificationView mNotificationView;
@@ -333,8 +329,6 @@
             /* Car Settings injected components. */
             CarServiceProvider carServiceProvider,
             Lazy<PowerManagerHelper> powerManagerHelperLazy,
-            FullscreenUserSwitcher fullscreenUserSwitcher,
-            SystemUIPrimaryWindowController systemUIPrimaryWindowController,
             CarNavigationBarController carNavigationBarController,
             FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
         super(
@@ -423,10 +417,9 @@
         mShadeController = shadeController;
         mCarServiceProvider = carServiceProvider;
         mPowerManagerHelperLazy = powerManagerHelperLazy;
-        mFullscreenUserSwitcher = fullscreenUserSwitcher;
-        mSystemUIPrimaryWindowController = systemUIPrimaryWindowController;
         mCarNavigationBarController = carNavigationBarController;
         mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
+        mScreenLifecycle = screenLifecycle;
     }
 
     @Override
@@ -434,18 +427,6 @@
         mDeviceIsSetUpForUser = mCarDeviceProvisionedController.isCurrentUserSetup();
         mIsUserSetupInProgress = mCarDeviceProvisionedController.isCurrentUserSetupInProgress();
 
-        // Need to initialize screen lifecycle before calling super.start - before switcher is
-        // created.
-        mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
-        mScreenLifecycle.addObserver(mScreenObserver);
-
-        // TODO: Remove the setup of user switcher from Car Status Bar.
-        mSystemUIPrimaryWindowController.attach();
-        mFullscreenUserSwitcher.setStatusBar(this);
-        mFullscreenUserSwitcher.setContainer(
-                mSystemUIPrimaryWindowController.getBaseLayout().findViewById(
-                        R.id.fullscreen_user_switcher_stub));
-
         // Notification bar related setup.
         mInitialBackgroundAlpha = (float) mContext.getResources().getInteger(
                 R.integer.config_initialNotificationBackgroundAlpha) / 100;
@@ -965,49 +946,6 @@
         }
     }
 
-    @Override
-    public void setLockscreenUser(int newUserId) {
-        super.setLockscreenUser(newUserId);
-        // Try to dismiss the keyguard after every user switch.
-        dismissKeyguardWhenUserSwitcherNotDisplayed();
-    }
-
-    @Override
-    public void onStateChanged(int newState) {
-        super.onStateChanged(newState);
-
-        if (newState != StatusBarState.FULLSCREEN_USER_SWITCHER) {
-            mFullscreenUserSwitcher.hide();
-        } else {
-            dismissKeyguardWhenUserSwitcherNotDisplayed();
-        }
-    }
-
-    final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
-        @Override
-        public void onScreenTurnedOn() {
-            dismissKeyguardWhenUserSwitcherNotDisplayed();
-        }
-    };
-
-    // We automatically dismiss keyguard unless user switcher is being shown on the keyguard.
-    private void dismissKeyguardWhenUserSwitcherNotDisplayed() {
-        if (!mUserSwitcherController.useFullscreenUserSwitcher()) {
-            return; // Not using the full screen user switcher.
-        }
-
-        if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER
-                && !mFullscreenUserSwitcher.isVisible()) {
-            // Current execution path continues to set state after this, thus we deffer the
-            // dismissal to the next execution cycle.
-            postDismissKeyguard(); // Dismiss the keyguard if switcher is not visible.
-        }
-    }
-
-    public void postDismissKeyguard() {
-        mHandler.post(this::dismissKeyguard);
-    }
-
     /**
      * Dismisses the keyguard and shows bouncer if authentication is necessary.
      */
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java
index 59f9f94..e1c051f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java
@@ -33,6 +33,9 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import java.util.HashSet;
+import java.util.Set;
+
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
@@ -42,7 +45,7 @@
 
     protected boolean mShouldHideNavBar;
     private final CarNavigationBarController mCarNavigationBarController;
-    private final FullscreenUserSwitcher mFullscreenUserSwitcher;
+    private Set<OnKeyguardCancelClickedListener> mKeygaurdCancelClickedListenerSet;
 
     @Inject
     public CarStatusBarKeyguardViewManager(Context context,
@@ -56,8 +59,7 @@
             NotificationShadeWindowController notificationShadeWindowController,
             KeyguardStateController keyguardStateController,
             NotificationMediaManager notificationMediaManager,
-            CarNavigationBarController carNavigationBarController,
-            FullscreenUserSwitcher fullscreenUserSwitcher) {
+            CarNavigationBarController carNavigationBarController) {
         super(context, callback, lockPatternUtils, sysuiStatusBarStateController,
                 configurationController, keyguardUpdateMonitor, navigationModeController,
                 dockManager, notificationShadeWindowController, keyguardStateController,
@@ -65,7 +67,7 @@
         mShouldHideNavBar = context.getResources()
                 .getBoolean(R.bool.config_hideNavWhenKeyguardBouncerShown);
         mCarNavigationBarController = carNavigationBarController;
-        mFullscreenUserSwitcher = fullscreenUserSwitcher;
+        mKeygaurdCancelClickedListenerSet = new HashSet<>();
     }
 
     @Override
@@ -95,7 +97,7 @@
      */
     @Override
     public void onCancelClicked() {
-        mFullscreenUserSwitcher.show();
+        mKeygaurdCancelClickedListenerSet.forEach(OnKeyguardCancelClickedListener::onCancelClicked);
     }
 
     /**
@@ -105,4 +107,31 @@
      */
     @Override
     public void onDensityOrFontScaleChanged() {  }
+
+    /**
+     * Add listener for keyguard cancel clicked.
+     */
+    public void addOnKeyguardCancelClickedListener(
+            OnKeyguardCancelClickedListener keyguardCancelClickedListener) {
+        mKeygaurdCancelClickedListenerSet.add(keyguardCancelClickedListener);
+    }
+
+    /**
+     * Remove listener for keyguard cancel clicked.
+     */
+    public void removeOnKeyguardCancelClickedListener(
+            OnKeyguardCancelClickedListener keyguardCancelClickedListener) {
+        mKeygaurdCancelClickedListenerSet.remove(keyguardCancelClickedListener);
+    }
+
+
+    /**
+     * Defines a callback for keyguard cancel button clicked listeners.
+     */
+    public interface OnKeyguardCancelClickedListener {
+        /**
+         * Called when keyguard cancel button is clicked.
+         */
+        void onCancelClicked();
+    }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index 07c42e1..aea4bd4 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -31,7 +31,6 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.car.CarServiceProvider;
-import com.android.systemui.car.SystemUIPrimaryWindowController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
@@ -205,8 +204,6 @@
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             CarServiceProvider carServiceProvider,
             Lazy<PowerManagerHelper> powerManagerHelperLazy,
-            FullscreenUserSwitcher fullscreenUserSwitcher,
-            SystemUIPrimaryWindowController systemUIPrimaryWindowController,
             CarNavigationBarController carNavigationBarController,
             FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
         return new CarStatusBar(
@@ -288,8 +285,6 @@
                 statusBarTouchableRegionManager,
                 carServiceProvider,
                 powerManagerHelperLazy,
-                fullscreenUserSwitcher,
-                systemUIPrimaryWindowController,
                 carNavigationBarController,
                 flingAnimationUtilsBuilder);
     }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
deleted file mode 100644
index 3cd66c2..0000000
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright (C) 2018 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.systemui.statusbar.car;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.car.Car;
-import android.car.trust.CarTrustAgentEnrollmentManager;
-import android.car.userlib.CarUserManagerHelper;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewStub;
-
-import androidx.recyclerview.widget.GridLayoutManager;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.R;
-import com.android.systemui.car.CarServiceProvider;
-import com.android.systemui.car.SystemUIPrimaryWindowController;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.car.CarTrustAgentUnlockDialogHelper.OnHideListener;
-import com.android.systemui.statusbar.car.UserGridRecyclerView.UserRecord;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Manages the fullscreen user switcher.
- */
-@Singleton
-public class FullscreenUserSwitcher {
-    private static final String TAG = FullscreenUserSwitcher.class.getSimpleName();
-    private final Context mContext;
-    private final Resources mResources;
-    private final UserManager mUserManager;
-    private final CarServiceProvider mCarServiceProvider;
-    private final CarTrustAgentUnlockDialogHelper mUnlockDialogHelper;
-    private final SystemUIPrimaryWindowController mSystemUIPrimaryWindowController;
-    private CarStatusBar mCarStatusBar;
-    private final int mShortAnimDuration;
-
-    private View mParent;
-    private UserGridRecyclerView mUserGridView;
-    private CarTrustAgentEnrollmentManager mEnrollmentManager;
-    private UserGridRecyclerView.UserRecord mSelectedUser;
-    private CarUserManagerHelper mCarUserManagerHelper;
-    private final BroadcastReceiver mUserUnlockReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "user 0 is unlocked, SharedPreference is accessible.");
-            }
-            showDialogForInitialUser();
-            mContext.unregisterReceiver(mUserUnlockReceiver);
-        }
-    };
-
-    @Inject
-    public FullscreenUserSwitcher(
-            Context context,
-            @Main Resources resources,
-            UserManager userManager,
-            CarServiceProvider carServiceProvider,
-            CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper,
-            SystemUIPrimaryWindowController systemUIPrimaryWindowController) {
-        mContext = context;
-        mResources = resources;
-        mUserManager = userManager;
-        mCarServiceProvider = carServiceProvider;
-        mUnlockDialogHelper = carTrustAgentUnlockDialogHelper;
-        mSystemUIPrimaryWindowController = systemUIPrimaryWindowController;
-
-        mShortAnimDuration = mResources.getInteger(android.R.integer.config_shortAnimTime);
-    }
-
-    /** Sets the status bar which gives an entry point to dismiss the keyguard. */
-    // TODO: Remove this in favor of a keyguard controller.
-    public void setStatusBar(CarStatusBar statusBar) {
-        mCarStatusBar = statusBar;
-    }
-
-    /** Returns {@code true} if the user switcher already has a parent view. */
-    public boolean isAttached() {
-        return mParent != null;
-    }
-
-    /** Sets the {@link ViewStub} to show the user switcher. */
-    public void setContainer(ViewStub containerStub) {
-        if (isAttached()) {
-            return;
-        }
-
-        mParent = containerStub.inflate();
-
-        View container = mParent.findViewById(R.id.container);
-
-        // Initialize user grid.
-        mUserGridView = container.findViewById(R.id.user_grid);
-        GridLayoutManager layoutManager = new GridLayoutManager(mContext,
-                mResources.getInteger(R.integer.user_fullscreen_switcher_num_col));
-        mUserGridView.setLayoutManager(layoutManager);
-        mUserGridView.buildAdapter();
-        mUserGridView.setUserSelectionListener(this::onUserSelected);
-        mCarUserManagerHelper = new CarUserManagerHelper(mContext);
-        mCarServiceProvider.addListener(
-                car -> mEnrollmentManager = (CarTrustAgentEnrollmentManager) car.getCarManager(
-                        Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE));
-
-        IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
-        if (mUserManager.isUserUnlocked(UserHandle.USER_SYSTEM)) {
-            // User0 is unlocked, switched to the initial user
-            showDialogForInitialUser();
-        } else {
-            // listen to USER_UNLOCKED
-            mContext.registerReceiverAsUser(mUserUnlockReceiver,
-                    UserHandle.getUserHandleForUid(UserHandle.USER_SYSTEM),
-                    filter,
-                    /* broadcastPermission= */ null,
-                    /* scheduler */ null);
-        }
-    }
-
-    private void showDialogForInitialUser() {
-        int initialUser = mCarUserManagerHelper.getInitialUser();
-        UserInfo initialUserInfo = mUserManager.getUserInfo(initialUser);
-        mSelectedUser = new UserRecord(initialUserInfo, UserRecord.FOREGROUND_USER);
-
-        // If the initial user has screen lock and trusted device, display the unlock dialog on the
-        // keyguard.
-        if (hasScreenLock(initialUser) && hasTrustedDevice(initialUser)) {
-            mUnlockDialogHelper.showUnlockDialogAfterDelay(initialUser,
-                    mOnHideListener);
-        } else {
-            // If no trusted device, dismiss the keyguard.
-            dismissUserSwitcher();
-        }
-    }
-
-    /**
-     * Makes user grid visible.
-     */
-    public void show() {
-        if (!isAttached()) {
-            return;
-        }
-        mParent.setVisibility(View.VISIBLE);
-        mSystemUIPrimaryWindowController.setWindowExpanded(true);
-    }
-
-    /**
-     * Hides the user grid.
-     */
-    public void hide() {
-        if (!isAttached()) {
-            return;
-        }
-        mParent.setVisibility(View.INVISIBLE);
-        mSystemUIPrimaryWindowController.setWindowExpanded(false);
-    }
-
-    /**
-     * @return {@code true} if user grid is visible, {@code false} otherwise.
-     */
-    public boolean isVisible() {
-        if (!isAttached()) {
-            return false;
-        }
-        return mParent.getVisibility() == View.VISIBLE;
-    }
-
-    /**
-     * Every time user clicks on an item in the switcher, if the clicked user has no trusted device,
-     * we hide the switcher, either gradually or immediately.
-     *
-     * If the user has trusted device, we show an unlock dialog to notify user the unlock state.
-     * When the unlock dialog is dismissed by user, we hide the unlock dialog and the switcher.
-     *
-     * We dismiss the entire keyguard when we hide the switcher if user clicked on the foreground
-     * user (user we're already logged in as).
-     */
-    private void onUserSelected(UserGridRecyclerView.UserRecord record) {
-        mSelectedUser = record;
-        if (record.mInfo != null) {
-            if (hasScreenLock(record.mInfo.id) && hasTrustedDevice(record.mInfo.id)) {
-                mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, mOnHideListener);
-                return;
-            }
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "no trusted device enrolled for uid: " + record.mInfo.id);
-            }
-        }
-        dismissUserSwitcher();
-    }
-
-    private void dismissUserSwitcher() {
-        if (mSelectedUser == null) {
-            Log.e(TAG, "Request to dismiss user switcher, but no user selected");
-            return;
-        }
-        if (mSelectedUser.mType == UserRecord.FOREGROUND_USER) {
-            hide();
-            mCarStatusBar.dismissKeyguard();
-            return;
-        }
-        // Switching is about to happen, since it takes time, fade out the switcher gradually.
-        fadeOut();
-    }
-
-    private void fadeOut() {
-        mUserGridView.animate()
-                .alpha(0.0f)
-                .setDuration(mShortAnimDuration)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        hide();
-                        mUserGridView.setAlpha(1.0f);
-                    }
-                });
-
-    }
-
-    private boolean hasScreenLock(int uid) {
-        LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
-        return lockPatternUtils.getCredentialTypeForUser(uid)
-                != LockPatternUtils.CREDENTIAL_TYPE_NONE;
-    }
-
-    private boolean hasTrustedDevice(int uid) {
-        if (mEnrollmentManager == null) { // car service not ready, so it cannot be available.
-            return false;
-        }
-        return !mEnrollmentManager.getEnrolledDeviceInfoForUser(uid).isEmpty();
-    }
-
-    private OnHideListener mOnHideListener = new OnHideListener() {
-        @Override
-        public void onHide(boolean dismissUserSwitcher) {
-            if (dismissUserSwitcher) {
-                dismissUserSwitcher();
-            } else {
-                // Re-draw the parent view, otherwise the unlock dialog will not be removed from
-                // the screen immediately.
-                mParent.invalidate();
-            }
-
-        }
-    };
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewController.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewController.java
new file mode 100644
index 0000000..6e0db4f
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewController.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 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.systemui.window;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+
+/**
+ * Owns a {@link View} that is present in SystemUIOverlayWindow.
+ */
+public class OverlayViewController {
+    private final int mStubId;
+    private final OverlayViewGlobalStateController mOverlayViewGlobalStateController;
+
+    private View mLayout;
+
+    public OverlayViewController(int stubId,
+            OverlayViewGlobalStateController overlayViewGlobalStateController) {
+        mLayout = null;
+        mStubId = stubId;
+        mOverlayViewGlobalStateController = overlayViewGlobalStateController;
+    }
+
+    /**
+     * Shows content of {@link OverlayViewController}.
+     *
+     * Should be used to show view externally and in particular by {@link OverlayViewMediator}.
+     */
+    public final void start() {
+        mOverlayViewGlobalStateController.showView(/* viewController= */ this, this::show);
+    }
+
+    /**
+     * Hides content of {@link OverlayViewController}.
+     *
+     * Should be used to hide view externally and in particular by {@link OverlayViewMediator}.
+     */
+    public final void stop() {
+        mOverlayViewGlobalStateController.hideView(/* viewController= */ this, this::hide);
+    }
+
+
+    /**
+     * Inflate layout owned by controller.
+     */
+    public final void inflate(ViewGroup baseLayout) {
+        ViewStub viewStub = baseLayout.findViewById(mStubId);
+        mLayout = viewStub.inflate();
+        onFinishInflate();
+    }
+
+    /**
+     * Called once inflate finishes.
+     */
+    protected void onFinishInflate() {
+        // no-op
+    }
+
+    /**
+     * Returns [@code true} if layout owned by controller has been inflated.
+     */
+    public final boolean isInflated() {
+        return mLayout != null;
+    }
+
+    private void show() {
+        if (mLayout == null) {
+            // layout must be inflated before show() is called.
+            return;
+        }
+        showInternal();
+    }
+
+    /**
+     * Subclasses should override this method to implement reveal animations and implement logic
+     * specific to when the layout owned by the controller is shown.
+     *
+     * Should only be overridden by Superclass but not called by any {@link OverlayViewMediator}.
+     */
+    protected void showInternal() {
+        mLayout.setVisibility(View.VISIBLE);
+    }
+
+    private void hide() {
+        if (mLayout == null) {
+            // layout must be inflated before hide() is called.
+            return;
+        }
+        hideInternal();
+    }
+
+    /**
+     * Subclasses should override this method to implement conceal animations and implement logic
+     * specific to when the layout owned by the controller is hidden.
+     *
+     * Should only be overridden by Superclass but not called by any {@link OverlayViewMediator}.
+     */
+    protected void hideInternal() {
+        mLayout.setVisibility(View.GONE);
+    }
+
+    /**
+     * Provides access to layout owned by controller.
+     */
+    protected final View getLayout() {
+        return mLayout;
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java
new file mode 100644
index 0000000..2d4a9e6
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 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.systemui.window;
+
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.navigationbar.car.CarNavigationBarController;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * This controller is responsible for the following:
+ * <p><ul>
+ * <li>Holds the global state for SystemUIOverlayWindow.
+ * <li>Allows {@link SystemUIOverlayWindowManager} to register {@link OverlayViewMediator}(s).
+ * <li>Enables {@link OverlayViewController)(s) to reveal/conceal themselves while respecting the
+ * global state of SystemUIOverlayWindow.
+ * </ul>
+ */
+@Singleton
+public class OverlayViewGlobalStateController {
+    private static final String TAG = OverlayViewGlobalStateController.class.getSimpleName();
+    private final SystemUIOverlayWindowController mSystemUIOverlayWindowController;
+    private final CarNavigationBarController mCarNavigationBarController;
+    @VisibleForTesting
+    Set<String> mShownSet;
+
+    @Inject
+    public OverlayViewGlobalStateController(
+            CarNavigationBarController carNavigationBarController,
+            SystemUIOverlayWindowController systemUIOverlayWindowController) {
+        mSystemUIOverlayWindowController = systemUIOverlayWindowController;
+        mSystemUIOverlayWindowController.attach();
+        mCarNavigationBarController = carNavigationBarController;
+        mShownSet = new HashSet<>();
+    }
+
+    /**
+     * Register {@link OverlayViewMediator} to use in SystemUIOverlayWindow.
+     */
+    public void registerMediator(OverlayViewMediator overlayViewMediator) {
+        Log.d(TAG, "Registering content mediator: " + overlayViewMediator.getClass().getName());
+
+        overlayViewMediator.registerListeners();
+        overlayViewMediator.setupOverlayContentViewControllers();
+    }
+
+    /**
+     * Show content in Overlay Window.
+     */
+    public void showView(OverlayViewController viewController, Runnable show) {
+        if (mShownSet.isEmpty()) {
+            mCarNavigationBarController.hideBars();
+            mSystemUIOverlayWindowController.setWindowExpanded(true);
+        }
+
+        if (!viewController.isInflated()) {
+            viewController.inflate(mSystemUIOverlayWindowController.getBaseLayout());
+        }
+
+        show.run();
+        mShownSet.add(viewController.getClass().getName());
+
+        Log.d(TAG, "Content shown: " + viewController.getClass().getName());
+    }
+
+    /**
+     * Hide content in Overlay Window.
+     */
+    public void hideView(OverlayViewController viewController, Runnable hide) {
+        if (!viewController.isInflated()) {
+            Log.d(TAG, "Content cannot be hidden since it isn't inflated: "
+                    + viewController.getClass().getName());
+            return;
+        }
+        if (!mShownSet.contains(viewController.getClass().getName())) {
+            Log.d(TAG, "Content cannot be hidden since it isn't shown: "
+                    + viewController.getClass().getName());
+            return;
+        }
+
+        hide.run();
+        mShownSet.remove(viewController.getClass().getName());
+
+        if (mShownSet.isEmpty()) {
+            mCarNavigationBarController.showBars();
+            mSystemUIOverlayWindowController.setWindowExpanded(false);
+        }
+
+        Log.d(TAG, "Content hidden: " + viewController.getClass().getName());
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewMediator.java
new file mode 100644
index 0000000..7c34fb4
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewMediator.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 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.systemui.window;
+
+/**
+ * Controls when to show and hide {@link OverlayViewController}(s).
+ */
+public interface OverlayViewMediator {
+
+    /**
+     * Register listeners that could use ContentVisibilityAdjuster to show/hide content.
+     */
+    void registerListeners();
+
+    /**
+     * Allows for post-inflation callbacks and listeners to be set inside required {@link
+     * OverlayViewController}(s).
+     */
+    void setupOverlayContentViewControllers();
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayWindowModule.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayWindowModule.java
new file mode 100644
index 0000000..b0e3089
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayWindowModule.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 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.systemui.window;
+
+import com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * Dagger injection module for {@link SystemUIOverlayWindowManager}
+ */
+@Module
+public abstract class OverlayWindowModule {
+    /** Inject into FullscreenUserSwitcherViewsMediator. */
+    @Binds
+    @IntoMap
+    @ClassKey(FullscreenUserSwitcherViewMediator.class)
+    public abstract OverlayViewMediator bindFullscreenUserSwitcherViewsMediator(
+            FullscreenUserSwitcherViewMediator overlayViewsMediator);
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java
similarity index 95%
rename from packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java
rename to packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java
index 3f55ac8..9c456ee 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.car;
+package com.android.systemui.window;
 
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
@@ -41,7 +41,7 @@
  * this window for the notification panel.
  */
 @Singleton
-public class SystemUIPrimaryWindowController implements
+public class SystemUIOverlayWindowController implements
         ConfigurationController.ConfigurationListener {
 
     private final Context mContext;
@@ -57,7 +57,7 @@
     private boolean mIsAttached = false;
 
     @Inject
-    public SystemUIPrimaryWindowController(
+    public SystemUIOverlayWindowController(
             Context context,
             @Main Resources resources,
             WindowManager windowManager,
@@ -77,7 +77,7 @@
 
         mLpChanged = new WindowManager.LayoutParams();
         mBaseLayout = (ViewGroup) LayoutInflater.from(context)
-                .inflate(R.layout.sysui_primary_window, /* root= */ null, false);
+                .inflate(R.layout.sysui_overlay_window, /* root= */ null, false);
 
         configurationController.addCallback(this);
     }
@@ -115,7 +115,7 @@
         mLp.gravity = Gravity.TOP;
         mLp.setFitInsetsTypes(/* types= */ 0);
         mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-        mLp.setTitle("SystemUIPrimaryWindow");
+        mLp.setTitle("SystemUIOverlayWindow");
         mLp.packageName = mContext.getPackageName();
         mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowManager.java b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowManager.java
new file mode 100644
index 0000000..af0f17d
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowManager.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 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.systemui.window;
+
+import android.content.Context;
+
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+/**
+ * Registers {@link OverlayViewMediator}(s) and synchronizes their calls to hide/show {@link
+ * OverlayViewController}(s) to allow for the correct visibility of system bars.
+ */
+@Singleton
+public class SystemUIOverlayWindowManager extends SystemUI {
+    private static final String TAG = "SystemUIOverlayWindowManager";
+    private final Map<Class<?>, Provider<OverlayViewMediator>>
+            mContentMediatorCreators;
+    private final OverlayViewGlobalStateController mOverlayViewGlobalStateController;
+
+    @Inject
+    public SystemUIOverlayWindowManager(
+            Context context,
+            Map<Class<?>, Provider<OverlayViewMediator>> contentMediatorCreators,
+            OverlayViewGlobalStateController overlayViewGlobalStateController) {
+        super(context);
+        mContentMediatorCreators = contentMediatorCreators;
+        mOverlayViewGlobalStateController = overlayViewGlobalStateController;
+    }
+
+    @Override
+    public void start() {
+        String[] names = mContext.getResources().getStringArray(
+                R.array.config_carSystemUIOverlayViewsMediators);
+        startServices(names);
+    }
+
+    private void startServices(String[] services) {
+        for (String clsName : services) {
+            try {
+                OverlayViewMediator obj = resolveContentMediator(clsName);
+                if (obj == null) {
+                    Constructor constructor = Class.forName(clsName).getConstructor(Context.class);
+                    obj = (OverlayViewMediator) constructor.newInstance(this);
+                }
+                mOverlayViewGlobalStateController.registerMediator(obj);
+            } catch (ClassNotFoundException
+                    | NoSuchMethodException
+                    | IllegalAccessException
+                    | InstantiationException
+                    | InvocationTargetException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+
+    private OverlayViewMediator resolveContentMediator(String className) {
+        return resolve(className, mContentMediatorCreators);
+    }
+
+    private <T> T resolve(String className, Map<Class<?>, Provider<T>> creators) {
+        try {
+            Class<?> clazz = Class.forName(className);
+            Provider<T> provider = creators.get(clazz);
+            return provider == null ? null : provider.get();
+        } catch (ClassNotFoundException e) {
+            return null;
+        }
+    }
+}
diff --git a/packages/CarSystemUI/tests/res/layout/overlay_view_controller_stub.xml b/packages/CarSystemUI/tests/res/layout/overlay_view_controller_stub.xml
new file mode 100644
index 0000000..5e5efe7
--- /dev/null
+++ b/packages/CarSystemUI/tests/res/layout/overlay_view_controller_stub.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ Copyright (C) 2020 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:id="@+id/overlay_view_controller_test">
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/tests/res/layout/overlay_view_controller_test.xml b/packages/CarSystemUI/tests/res/layout/overlay_view_controller_test.xml
new file mode 100644
index 0000000..165193e
--- /dev/null
+++ b/packages/CarSystemUI/tests/res/layout/overlay_view_controller_test.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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.
+  -->
+
+<!-- Fullscreen views in sysui should be listed here in increasing Z order. -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:background="@android:color/transparent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ViewStub android:id="@+id/overlay_view_controller_stub"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:layout="@layout/overlay_view_controller_stub"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java
new file mode 100644
index 0000000..3be9626
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2020 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.systemui.window;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.tests.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class OverlayViewControllerTest extends SysuiTestCase {
+    private MockOverlayViewController mOverlayViewController;
+    private ViewGroup mBaseLayout;
+
+    @Mock
+    private OverlayViewGlobalStateController mOverlayViewGlobalStateController;
+
+    @Captor
+    private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(/* testClass= */ this);
+
+        mOverlayViewController = new MockOverlayViewController(R.id.overlay_view_controller_stub,
+                mOverlayViewGlobalStateController);
+
+        mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate(
+                R.layout.overlay_view_controller_test, /* root= */ null);
+    }
+
+    @Test
+    public void inflate_layoutInitialized() {
+        mOverlayViewController.inflate(mBaseLayout);
+
+        assertThat(mOverlayViewController.getLayout().getId()).isEqualTo(
+                R.id.overlay_view_controller_test);
+    }
+
+    @Test
+    public void inflate_onFinishInflateCalled() {
+        mOverlayViewController.inflate(mBaseLayout);
+
+        assertThat(mOverlayViewController.mOnFinishInflateCalled).isTrue();
+    }
+
+    @Test
+    public void start_viewInflated_viewShown() {
+        mOverlayViewController.inflate(mBaseLayout);
+
+        mOverlayViewController.start();
+
+        verify(mOverlayViewGlobalStateController).showView(eq(mOverlayViewController),
+                mRunnableArgumentCaptor.capture());
+
+        mRunnableArgumentCaptor.getValue().run();
+
+        assertThat(mOverlayViewController.mShowInternalCalled).isTrue();
+    }
+
+    @Test
+    public void stop_viewInflated_viewHidden() {
+        mOverlayViewController.inflate(mBaseLayout);
+
+        mOverlayViewController.stop();
+
+        verify(mOverlayViewGlobalStateController).hideView(eq(mOverlayViewController),
+                mRunnableArgumentCaptor.capture());
+
+        mRunnableArgumentCaptor.getValue().run();
+
+        assertThat(mOverlayViewController.mHideInternalCalled).isTrue();
+    }
+
+    @Test
+    public void start_viewNotInflated_viewNotShown() {
+        mOverlayViewController.start();
+
+        verify(mOverlayViewGlobalStateController).showView(eq(mOverlayViewController),
+                mRunnableArgumentCaptor.capture());
+
+        mRunnableArgumentCaptor.getValue().run();
+
+        assertThat(mOverlayViewController.mShowInternalCalled).isFalse();
+    }
+
+    @Test
+    public void stop_viewNotInflated_viewNotHidden() {
+        mOverlayViewController.stop();
+
+        verify(mOverlayViewGlobalStateController).hideView(eq(mOverlayViewController),
+                mRunnableArgumentCaptor.capture());
+
+        mRunnableArgumentCaptor.getValue().run();
+
+        assertThat(mOverlayViewController.mHideInternalCalled).isFalse();
+    }
+
+    private static class MockOverlayViewController extends OverlayViewController {
+        boolean mOnFinishInflateCalled = false;
+        boolean mShowInternalCalled = false;
+        boolean mHideInternalCalled = false;
+
+        MockOverlayViewController(int stubId,
+                OverlayViewGlobalStateController overlayViewGlobalStateController) {
+            super(stubId, overlayViewGlobalStateController);
+        }
+
+        @Override
+        protected void onFinishInflate() {
+            mOnFinishInflateCalled = true;
+        }
+
+        @Override
+        protected void showInternal() {
+            mShowInternalCalled = true;
+        }
+
+        @Override
+        protected void hideInternal() {
+            mHideInternalCalled = true;
+        }
+    }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewGlobalStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewGlobalStateControllerTest.java
new file mode 100644
index 0000000..03354d1
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewGlobalStateControllerTest.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2020 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.systemui.window;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.navigationbar.car.CarNavigationBarController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
+    private static final String MOCK_OVERLAY_VIEW_CONTROLLER_NAME = "OverlayViewController";
+
+    private OverlayViewGlobalStateController mOverlayViewGlobalStateController;
+    private ViewGroup mBaseLayout;
+
+    @Mock
+    private CarNavigationBarController mCarNavigationBarController;
+    @Mock
+    private SystemUIOverlayWindowController mSystemUIOverlayWindowController;
+    @Mock
+    private OverlayViewMediator mOverlayViewMediator;
+    @Mock
+    private OverlayViewController mOverlayViewController;
+    @Mock
+    private Runnable mRunnable;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(/* testClass= */ this);
+
+        mOverlayViewGlobalStateController = new OverlayViewGlobalStateController(
+                mCarNavigationBarController, mSystemUIOverlayWindowController);
+
+        verify(mSystemUIOverlayWindowController).attach();
+
+        mBaseLayout = new FrameLayout(mContext);
+
+        when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout);
+    }
+
+    @Test
+    public void registerMediator_overlayViewMediatorListenersRegistered() {
+        mOverlayViewGlobalStateController.registerMediator(mOverlayViewMediator);
+
+        verify(mOverlayViewMediator).registerListeners();
+    }
+
+    @Test
+    public void registerMediator_overlayViewMediatorViewControllerSetup() {
+        mOverlayViewGlobalStateController.registerMediator(mOverlayViewMediator);
+
+        verify(mOverlayViewMediator).setupOverlayContentViewControllers();
+    }
+
+    @Test
+    public void showView_nothingAlreadyShown_navigationBarsHidden() {
+        mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+
+        verify(mCarNavigationBarController).hideBars();
+    }
+
+    @Test
+    public void showView_nothingAlreadyShown_windowIsExpanded() {
+        mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+
+        verify(mSystemUIOverlayWindowController).setWindowExpanded(true);
+    }
+
+    @Test
+    public void showView_somethingAlreadyShown_navigationBarsHidden() {
+        mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+
+        verify(mCarNavigationBarController, never()).hideBars();
+    }
+
+    @Test
+    public void showView_somethingAlreadyShown_windowIsExpanded() {
+        mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+
+        verify(mSystemUIOverlayWindowController, never()).setWindowExpanded(true);
+    }
+
+    @Test
+    public void showView_viewControllerNotInflated_inflateViewController() {
+        when(mOverlayViewController.isInflated()).thenReturn(false);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+
+        verify(mOverlayViewController).inflate(mBaseLayout);
+    }
+
+    @Test
+    public void showView_viewControllerInflated_inflateViewControllerNotCalled() {
+        when(mOverlayViewController.isInflated()).thenReturn(true);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+
+        verify(mOverlayViewController, never()).inflate(mBaseLayout);
+    }
+
+    @Test
+    public void showView_showRunnableCalled() {
+        mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+
+        verify(mRunnable).run();
+    }
+
+    @Test
+    public void showView_overlayViewControllerAddedToShownSet() {
+        mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+
+        assertThat(mOverlayViewGlobalStateController.mShownSet.contains(
+                mOverlayViewController.getClass().getName())).isTrue();
+    }
+
+    @Test
+    public void hideView_viewControllerNotInflated_hideRunnableNotCalled() {
+        when(mOverlayViewController.isInflated()).thenReturn(false);
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+
+        verify(mRunnable, never()).run();
+    }
+
+    @Test
+    public void hideView_nothingShown_hideRunnableNotCalled() {
+        when(mOverlayViewController.isInflated()).thenReturn(true);
+        mOverlayViewGlobalStateController.mShownSet.clear();
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+
+        verify(mRunnable, never()).run();
+    }
+
+    @Test
+    public void hideView_viewControllerNotShown_hideRunnableNotCalled() {
+        when(mOverlayViewController.isInflated()).thenReturn(true);
+        mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME);
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+
+        verify(mRunnable, never()).run();
+    }
+
+    @Test
+    public void hideView_viewControllerShown_hideRunnableCalled() {
+        when(mOverlayViewController.isInflated()).thenReturn(true);
+        mOverlayViewGlobalStateController.mShownSet.add(
+                mOverlayViewController.getClass().getName());
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+
+        verify(mRunnable).run();
+    }
+
+    @Test
+    public void hideView_viewControllerOnlyShown_nothingShown() {
+        when(mOverlayViewController.isInflated()).thenReturn(true);
+        mOverlayViewGlobalStateController.mShownSet.add(
+                mOverlayViewController.getClass().getName());
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+
+        assertThat(mOverlayViewGlobalStateController.mShownSet.isEmpty()).isTrue();
+    }
+
+    @Test
+    public void hideView_viewControllerNotOnlyShown_navigationBarNotShown() {
+        when(mOverlayViewController.isInflated()).thenReturn(true);
+        mOverlayViewGlobalStateController.mShownSet.add(
+                mOverlayViewController.getClass().getName());
+        mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME);
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+
+        verify(mCarNavigationBarController, never()).showBars();
+    }
+
+    @Test
+    public void hideView_viewControllerNotOnlyShown_windowNotCollapsed() {
+        when(mOverlayViewController.isInflated()).thenReturn(true);
+        mOverlayViewGlobalStateController.mShownSet.add(
+                mOverlayViewController.getClass().getName());
+        mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME);
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+
+        verify(mSystemUIOverlayWindowController, never()).setWindowExpanded(false);
+    }
+
+    @Test
+    public void hideView_viewControllerOnlyShown_navigationBarShown() {
+        when(mOverlayViewController.isInflated()).thenReturn(true);
+        mOverlayViewGlobalStateController.mShownSet.add(
+                mOverlayViewController.getClass().getName());
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+
+        verify(mCarNavigationBarController).showBars();
+    }
+
+    @Test
+    public void hideView_viewControllerOnlyShown_windowCollapsed() {
+        when(mOverlayViewController.isInflated()).thenReturn(true);
+        mOverlayViewGlobalStateController.mShownSet.add(
+                mOverlayViewController.getClass().getName());
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+
+        verify(mSystemUIOverlayWindowController).setWindowExpanded(false);
+    }
+}