Merge "Use abstractions to allow FullscreenUserSwitcher take advantage of SystemUIOverlayWindow." into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
995e2de829
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user