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);
+ }
+}