diff options
26 files changed, 1385 insertions, 371 deletions
diff --git a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml index 2fe9d21b0489..55207b335aed 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 9c598d71bd22..0c6f322ca261 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 index cc36e87eb480..cc36e87eb480 100644 --- a/packages/CarSystemUI/res/layout/sysui_primary_window.xml +++ b/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 825b2813d47d..aaa65de2fa1d 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 8f9d7ed8c9b7..c01088108a9b 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.toast.ToastUI; 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 @@ import dagger.multibindings.IntoMap; /** 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 @@ public abstract class CarSystemUIBinder { @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 index 07dbd667acf4..597716569e9b 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 @@ class CarTrustAgentUnlockDialogHelper extends BroadcastReceiver{ 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 000000000000..45ceb6d1551f --- /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 000000000000..627729768e88 --- /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 index 7dd3be4b2c11..58add179886c 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.LayoutInflater; 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 @@ public class UserGridRecyclerView extends RecyclerView { /** * Initializes the adapter that populates the grid layout - * - * @return the adapter */ public void buildAdapter() { List<UserRecord> userRecords = createUserRecords(getUsersForUserGrid()); @@ -236,10 +233,16 @@ public class UserGridRecyclerView extends RecyclerView { 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 @@ public class UserGridRecyclerView extends RecyclerView { 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 index 9018290f4955..dc5953e38ccb 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 a56c4eda44da..67e9da429c36 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 @@ public class CarNavigationBarController { 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 f9cfafa5c471..31965c5fc022 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.annotation.VisibleForTesting; 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 0374a5cef0ae..83248f4d867b 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.bubbles.BubbleController; 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.NotificationMediaManager; 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 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt private FlingAnimationUtils mFlingAnimationUtils; private NotificationDataManager mNotificationDataManager; private NotificationClickHandlerFactory mNotificationClickHandlerFactory; - private ScreenLifecycle mScreenLifecycle; // The container for the notifications. private CarNotificationView mNotificationView; @@ -333,8 +329,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt /* Car Settings injected components. */ CarServiceProvider carServiceProvider, Lazy<PowerManagerHelper> powerManagerHelperLazy, - FullscreenUserSwitcher fullscreenUserSwitcher, - SystemUIPrimaryWindowController systemUIPrimaryWindowController, CarNavigationBarController carNavigationBarController, FlingAnimationUtils.Builder flingAnimationUtilsBuilder) { super( @@ -423,10 +417,9 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt mShadeController = shadeController; mCarServiceProvider = carServiceProvider; mPowerManagerHelperLazy = powerManagerHelperLazy; - mFullscreenUserSwitcher = fullscreenUserSwitcher; - mSystemUIPrimaryWindowController = systemUIPrimaryWindowController; mCarNavigationBarController = carNavigationBarController; mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder; + mScreenLifecycle = screenLifecycle; } @Override @@ -434,18 +427,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt } } - @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 59f9f94c4d05..e1c051f5012d 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.phone.StatusBarKeyguardViewManager; 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 @@ public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManage protected boolean mShouldHideNavBar; private final CarNavigationBarController mCarNavigationBarController; - private final FullscreenUserSwitcher mFullscreenUserSwitcher; + private Set<OnKeyguardCancelClickedListener> mKeygaurdCancelClickedListenerSet; @Inject public CarStatusBarKeyguardViewManager(Context context, @@ -56,8 +59,7 @@ public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManage 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 @@ public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManage mShouldHideNavBar = context.getResources() .getBoolean(R.bool.config_hideNavWhenKeyguardBouncerShown); mCarNavigationBarController = carNavigationBarController; - mFullscreenUserSwitcher = fullscreenUserSwitcher; + mKeygaurdCancelClickedListenerSet = new HashSet<>(); } @Override @@ -95,7 +97,7 @@ public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManage */ @Override public void onCancelClicked() { - mFullscreenUserSwitcher.show(); + mKeygaurdCancelClickedListenerSet.forEach(OnKeyguardCancelClickedListener::onCancelClicked); } /** @@ -105,4 +107,31 @@ public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManage */ @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 07c42e17d006..aea4bd497527 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.assist.AssistManager; 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 @@ public class CarStatusBarModule { StatusBarTouchableRegionManager statusBarTouchableRegionManager, CarServiceProvider carServiceProvider, Lazy<PowerManagerHelper> powerManagerHelperLazy, - FullscreenUserSwitcher fullscreenUserSwitcher, - SystemUIPrimaryWindowController systemUIPrimaryWindowController, CarNavigationBarController carNavigationBarController, FlingAnimationUtils.Builder flingAnimationUtilsBuilder) { return new CarStatusBar( @@ -288,8 +285,6 @@ public class CarStatusBarModule { 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 3cd66c232717..000000000000 --- 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 000000000000..6e0db4f317ba --- /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 000000000000..2d4a9e6331d2 --- /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 000000000000..7c34fb494de6 --- /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 000000000000..b0e308966477 --- /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 index 3f55ac8ccace..9c456eecc1ac 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 @@ import javax.inject.Singleton; * 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 @@ public class SystemUIPrimaryWindowController implements private boolean mIsAttached = false; @Inject - public SystemUIPrimaryWindowController( + public SystemUIOverlayWindowController( Context context, @Main Resources resources, WindowManager windowManager, @@ -77,7 +77,7 @@ public class SystemUIPrimaryWindowController implements 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 @@ public class SystemUIPrimaryWindowController implements 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 000000000000..af0f17d50ee2 --- /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 000000000000..5e5efe7614fc --- /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 000000000000..165193e86c96 --- /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 000000000000..3be962627f62 --- /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 000000000000..03354d1aa9d7 --- /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); + } +} |