diff options
59 files changed, 3065 insertions, 654 deletions
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java index d755d3877275..eac3bee8e790 100644 --- a/core/java/android/service/voice/HotwordDetectionService.java +++ b/core/java/android/service/voice/HotwordDetectionService.java @@ -87,6 +87,14 @@ public abstract class HotwordDetectionService extends Service { public static final int MAXIMUM_NUMBER_OF_INITIALIZATION_STATUS_CUSTOM_ERROR = 2; /** + * Feature flag for Attention Service. + * + * TODO(b/247920386): Add TestApi annotation + * @hide + */ + public static final boolean ENABLE_PROXIMITY_RESULT = false; + + /** * Indicates that the updated status is successful. */ public static final int INITIALIZATION_STATUS_SUCCESS = 0; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index ff4b2edb7310..f8799945134f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -24,6 +24,7 @@ import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTas import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.TaskInfo; +import android.content.ComponentName; import android.content.Context; import android.os.RemoteException; import android.util.Slog; @@ -327,6 +328,28 @@ public class RecentTasksController implements TaskStackListenerCallback, return recentTasks; } + /** + * Find the background task that match the given component. + */ + @Nullable + public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName) { + if (componentName == null) { + return null; + } + List<ActivityManager.RecentTaskInfo> tasks = getRawRecentTasks(Integer.MAX_VALUE, + ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); + for (int i = 0; i < tasks.size(); i++) { + final ActivityManager.RecentTaskInfo task = tasks.get(i); + if (task.isVisible) { + continue; + } + if (componentName.equals(task.baseIntent.getComponent())) { + return task; + } + } + return null; + } + public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 991f136c0055..07a6895e2720 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -385,6 +385,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override public void onAnimationCancelled(boolean isKeyguardOccluded) { + final WindowContainerTransaction evictWct = new WindowContainerTransaction(); + mStageCoordinator.prepareEvictInvisibleChildTasks(evictWct); + mSyncQueue.queue(evictWct); } }; options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, @@ -472,8 +475,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the - // split. + // split and there is no reusable background task. if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) { + final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent() + ? mRecentTasksOptional.get().findTaskInBackground( + intent.getIntent().getComponent()) + : null; + if (taskInfo != null) { + startTask(taskInfo.taskId, position, options); + return; + } fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index 818248439d0a..9f275afa3765 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -813,7 +813,7 @@ -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt @@ -828,7 +828,7 @@ -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index 0c57b934d27e..8388b67c1fa1 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -103,6 +103,7 @@ android:layout_width="match_parent" android:layout_weight="1" android:background="@android:color/transparent" + android:visibility="invisible" android:clipChildren="false" android:clipToPadding="false" /> </LinearLayout> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt index b78fa9a6b392..71470e8870de 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt @@ -57,11 +57,10 @@ data class KeyguardFaceListenModel( val faceLockedOut: Boolean, val fpLockedOut: Boolean, val goingToSleep: Boolean, - val keyguardAwakeExcludingBouncerShowing: Boolean, + val keyguardAwake: Boolean, val keyguardGoingAway: Boolean, val listeningForFaceAssistant: Boolean, val occludingAppRequestingFaceAuth: Boolean, - val onlyFaceEnrolled: Boolean, val primaryUser: Boolean, val scanningAllowedByStrongAuth: Boolean, val secureCameraLaunched: Boolean, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index f73c98e4971b..2bdb1b894c4a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -22,8 +22,6 @@ import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA; import static java.lang.Integer.max; @@ -87,8 +85,8 @@ import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter; import com.android.systemui.user.data.source.UserRecord; import com.android.systemui.util.settings.GlobalSettings; @@ -1098,6 +1096,7 @@ public class KeyguardSecurityContainer extends FrameLayout { return; } + mView.setAlpha(1f); mUserSwitcherViewGroup.setAlpha(0f); ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA, 1f); @@ -1137,7 +1136,7 @@ public class KeyguardSecurityContainer extends FrameLayout { KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor); - BaseUserAdapter adapter = new BaseUserAdapter(mUserSwitcherController) { + BaseUserSwitcherAdapter adapter = new BaseUserSwitcherAdapter(mUserSwitcherController) { @Override public View getView(int position, View convertView, ViewGroup parent) { UserRecord item = getItem(position); @@ -1172,8 +1171,7 @@ public class KeyguardSecurityContainer extends FrameLayout { } textView.setSelected(item == currentUser); view.setEnabled(item.isSwitchToEnabled); - view.setAlpha(view.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : - USER_SWITCH_DISABLED_ALPHA); + UserSwitcherController.setSelectableAlpha(view); return view; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index d8cffd7984ba..5995e859c786 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -107,6 +107,14 @@ public class KeyguardSimPukViewController } @Override + public void onResume(int reason) { + super.onResume(reason); + if (mShowDefaultMessage) { + showDefaultMessage(); + } + } + + @Override void resetState() { super.resetState(); mStateMachine.reset(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 6745cab2fcb5..6eef3b33cf8f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -2593,11 +2593,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED; - // mKeyguardIsVisible is true even when the bouncer is shown, we don't want to run face auth - // on bouncer if both fp and fingerprint are enrolled. - final boolean awakeKeyguardExcludingBouncerShowing = mKeyguardIsVisible - && mDeviceInteractive && !mGoingToSleep - && !statusBarShadeLocked && !mBouncerFullyShown; + final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep + && !statusBarShadeLocked; final int user = getCurrentUser(); final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user); final boolean isLockDown = @@ -2637,16 +2634,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean faceDisabledForUser = isFaceDisabled(user); final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user); final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant(); - final boolean onlyFaceEnrolled = isOnlyFaceEnrolled(); final boolean fpOrFaceIsLockedOut = isFaceLockedOut() || fpLockedout; // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. final boolean shouldListen = - ((mBouncerFullyShown && !mGoingToSleep && onlyFaceEnrolled) + (mBouncerFullyShown && !mGoingToSleep || mAuthInterruptActive || mOccludingAppRequestingFace - || awakeKeyguardExcludingBouncerShowing + || awakeKeyguard || shouldListenForFaceAssistant || mAuthController.isUdfpsFingerDown() || mUdfpsBouncerShowing) @@ -2672,11 +2668,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab isFaceLockedOut(), fpLockedout, mGoingToSleep, - awakeKeyguardExcludingBouncerShowing, + awakeKeyguard, mKeyguardGoingAway, shouldListenForFaceAssistant, mOccludingAppRequestingFace, - onlyFaceEnrolled, mIsPrimaryUser, strongAuthAllowsScanning, mSecureCameraLaunched, @@ -2686,11 +2681,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return shouldListen; } - private boolean isOnlyFaceEnrolled() { - return isFaceEnrolled() - && !getCachedIsUnlockWithFingerprintPossible(sCurrentUser); - } - private void maybeLogListenerModelData(KeyguardListenModel model) { mLogger.logKeyguardListenerModel(model); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 0469152de776..443d2774f0e0 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -41,6 +41,7 @@ import com.android.systemui.dreams.dagger.DreamModule; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FlagsModule; import com.android.systemui.fragments.FragmentService; +import com.android.systemui.keyguard.data.BouncerViewModule; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.media.dagger.MediaProjectionModule; import com.android.systemui.model.SysUiState; @@ -116,6 +117,7 @@ import dagger.Provides; AppOpsModule.class, AssistModule.class, BiometricsModule.class, + BouncerViewModule.class, ClockModule.class, CoroutinesModule.class, DreamModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index d7b7777559da..733a80dd7f69 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -35,6 +35,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.complication.ComplicationHostViewController; import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.dreams.dagger.DreamOverlayModule; +import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor; import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -73,6 +74,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates). private final Handler mHandler; private final int mDreamOverlayMaxTranslationY; + private final BouncerCallbackInteractor mBouncerCallbackInteractor; private long mJitterStartTimeMillis; @@ -131,7 +133,8 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve @Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset, @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long burnInProtectionUpdateInterval, - @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter) { + @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter, + BouncerCallbackInteractor bouncerCallbackInteractor) { super(containerView); mDreamOverlayContentView = contentView; mStatusBarViewController = statusBarViewController; @@ -151,6 +154,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve mMaxBurnInOffset = maxBurnInOffset; mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval; mMillisUntilFullJitter = millisUntilFullJitter; + mBouncerCallbackInteractor = bouncerCallbackInteractor; } @Override @@ -167,6 +171,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve if (bouncer != null) { bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback); } + mBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback); } @Override @@ -176,6 +181,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve if (bouncer != null) { bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback); } + mBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback); } View getContainerView() { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java index 21a51d1096d5..c07d4022df76 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java @@ -18,13 +18,21 @@ package com.android.systemui.dreams.complication; import static com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent.DreamMediaEntryModule.DREAM_MEDIA_ENTRY_VIEW; import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_MEDIA_ENTRY_LAYOUT_PARAMS; +import static com.android.systemui.flags.Flags.DREAM_MEDIA_TAP_TO_OPEN; +import android.app.PendingIntent; import android.util.Log; import android.view.View; +import com.android.systemui.ActivityIntentHelper; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.media.MediaCarouselController; import com.android.systemui.media.dream.MediaDreamComplication; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ViewController; import javax.inject.Inject; @@ -87,6 +95,15 @@ public class DreamMediaEntryComplication implements Complication { private final DreamOverlayStateController mDreamOverlayStateController; private final MediaDreamComplication mMediaComplication; + private final MediaCarouselController mMediaCarouselController; + + private final ActivityStarter mActivityStarter; + private final ActivityIntentHelper mActivityIntentHelper; + private final KeyguardStateController mKeyguardStateController; + private final NotificationLockscreenUserManager mLockscreenUserManager; + + private final FeatureFlags mFeatureFlags; + private boolean mIsTapToOpenEnabled; private boolean mMediaComplicationAdded; @@ -94,15 +111,28 @@ public class DreamMediaEntryComplication implements Complication { DreamMediaEntryViewController( @Named(DREAM_MEDIA_ENTRY_VIEW) View view, DreamOverlayStateController dreamOverlayStateController, - MediaDreamComplication mediaComplication) { + MediaDreamComplication mediaComplication, + MediaCarouselController mediaCarouselController, + ActivityStarter activityStarter, + ActivityIntentHelper activityIntentHelper, + KeyguardStateController keyguardStateController, + NotificationLockscreenUserManager lockscreenUserManager, + FeatureFlags featureFlags) { super(view); mDreamOverlayStateController = dreamOverlayStateController; mMediaComplication = mediaComplication; + mMediaCarouselController = mediaCarouselController; + mActivityStarter = activityStarter; + mActivityIntentHelper = activityIntentHelper; + mKeyguardStateController = keyguardStateController; + mLockscreenUserManager = lockscreenUserManager; + mFeatureFlags = featureFlags; mView.setOnClickListener(this::onClickMediaEntry); } @Override protected void onViewAttached() { + mIsTapToOpenEnabled = mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN); } @Override @@ -113,6 +143,31 @@ public class DreamMediaEntryComplication implements Complication { private void onClickMediaEntry(View v) { if (DEBUG) Log.d(TAG, "media entry complication tapped"); + if (mIsTapToOpenEnabled) { + final PendingIntent clickIntent = + mMediaCarouselController.getCurrentVisibleMediaContentIntent(); + + if (clickIntent == null) { + return; + } + + // See StatusBarNotificationActivityStarter#onNotificationClicked + final boolean showOverLockscreen = mKeyguardStateController.isShowing() + && mActivityIntentHelper.wouldShowOverLockscreen(clickIntent.getIntent(), + mLockscreenUserManager.getCurrentUserId()); + + if (showOverLockscreen) { + mActivityStarter.startActivity(clickIntent.getIntent(), + /* dismissShade */ true, + /* animationController */ null, + /* showOverLockscreenWhenLocked */ true); + } else { + mActivityStarter.postStartActivityDismissingKeyguard(clickIntent, null); + } + + return; + } + if (!mMediaComplicationAdded) { addMediaComplication(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 44feac13198f..48f5f9eda909 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -104,6 +104,10 @@ public class Flags { public static final UnreleasedFlag MODERN_USER_SWITCHER_ACTIVITY = new UnreleasedFlag(209, true); + /** Whether the new implementation of UserSwitcherController should be used. */ + public static final UnreleasedFlag REFACTORED_USER_SWITCHER_CONTROLLER = + new UnreleasedFlag(210, false); + /***************************************/ // 300 - power menu public static final ReleasedFlag POWER_MENU_LITE = @@ -196,7 +200,8 @@ public class Flags { public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901); public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903); public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904); - public static final UnreleasedFlag MEDIA_DREAM_COMPLICATION = new UnreleasedFlag(905); + public static final UnreleasedFlag DREAM_MEDIA_COMPLICATION = new UnreleasedFlag(905); + public static final UnreleasedFlag DREAM_MEDIA_TAP_TO_OPEN = new UnreleasedFlag(906); // 1000 - dock public static final ReleasedFlag SIMULATE_DOCK_THROUGH_CHARGING = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt new file mode 100644 index 000000000000..99ae85d7a548 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 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.keyguard.data + +import android.view.KeyEvent +import com.android.systemui.dagger.SysUISingleton +import java.lang.ref.WeakReference +import javax.inject.Inject + +/** An abstraction to interface with the ui layer, without changing state. */ +interface BouncerView { + var delegate: BouncerViewDelegate? +} + +/** A lightweight class to hold reference to the ui delegate. */ +@SysUISingleton +class BouncerViewImpl @Inject constructor() : BouncerView { + private var _delegate: WeakReference<BouncerViewDelegate?> = WeakReference(null) + override var delegate: BouncerViewDelegate? + get() = _delegate.get() + set(value) { + _delegate = WeakReference(value) + } +} + +/** An abstraction that implements view logic. */ +interface BouncerViewDelegate { + fun isFullScreenBouncer(): Boolean + fun shouldDismissOnMenuPressed(): Boolean + fun interceptMediaKey(event: KeyEvent?): Boolean + fun dispatchBackKeyEventPreIme(): Boolean + fun showNextSecurityScreenOrFinish(): Boolean + fun resume() +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt new file mode 100644 index 000000000000..390c54e0350a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 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.keyguard.data + +import dagger.Binds +import dagger.Module + +@Module +interface BouncerViewModule { + /** Binds BouncerView to BouncerViewImpl and makes it injectable. */ + @Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt new file mode 100644 index 000000000000..543389e0a7cd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2022 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.keyguard.data.repository + +import android.hardware.biometrics.BiometricSourceType +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback +import com.android.keyguard.ViewMediatorCallback +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel +import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel +import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel +import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** Encapsulates app state for the lock screen bouncer. */ +@SysUISingleton +class KeyguardBouncerRepository +@Inject +constructor( + private val viewMediatorCallback: ViewMediatorCallback, + keyguardUpdateMonitor: KeyguardUpdateMonitor, +) { + var bouncerPromptReason: Int? = null + /** Determines if we want to instantaneously show the bouncer instead of translating. */ + private val _isScrimmed = MutableStateFlow(false) + val isScrimmed = _isScrimmed.asStateFlow() + /** Set amount of how much of the bouncer is showing on the screen */ + private val _expansionAmount = MutableStateFlow(EXPANSION_HIDDEN) + val expansionAmount = _expansionAmount.asStateFlow() + private val _isVisible = MutableStateFlow(false) + val isVisible = _isVisible.asStateFlow() + private val _show = MutableStateFlow<KeyguardBouncerModel?>(null) + val show = _show.asStateFlow() + private val _showingSoon = MutableStateFlow(false) + val showingSoon = _showingSoon.asStateFlow() + private val _hide = MutableStateFlow(false) + val hide = _hide.asStateFlow() + private val _startingToHide = MutableStateFlow(false) + val startingToHide = _startingToHide.asStateFlow() + private val _onDismissAction = MutableStateFlow<BouncerCallbackActionsModel?>(null) + val onDismissAction = _onDismissAction.asStateFlow() + private val _disappearAnimation = MutableStateFlow<Runnable?>(null) + val startingDisappearAnimation = _disappearAnimation.asStateFlow() + private val _keyguardPosition = MutableStateFlow(0f) + val keyguardPosition = _keyguardPosition.asStateFlow() + private val _resourceUpdateRequests = MutableStateFlow(false) + val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow() + private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null) + val showMessage = _showMessage.asStateFlow() + private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null) + /** Determines if user is already unlocked */ + val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow() + private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null) + val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow() + private val _onScreenTurnedOff = MutableStateFlow(false) + val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow() + + val bouncerErrorMessage: CharSequence? + get() = viewMediatorCallback.consumeCustomMessage() + + init { + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onStrongAuthStateChanged(userId: Int) { + bouncerPromptReason = viewMediatorCallback.bouncerPromptReason + } + + override fun onLockedOutStateChanged(type: BiometricSourceType) { + if (type == BiometricSourceType.FINGERPRINT) { + bouncerPromptReason = viewMediatorCallback.bouncerPromptReason + } + } + } + + keyguardUpdateMonitor.registerCallback(callback) + } + + fun setScrimmed(isScrimmed: Boolean) { + _isScrimmed.value = isScrimmed + } + + fun setExpansion(expansion: Float) { + _expansionAmount.value = expansion + } + + fun setVisible(isVisible: Boolean) { + _isVisible.value = isVisible + } + + fun setShow(keyguardBouncerModel: KeyguardBouncerModel?) { + _show.value = keyguardBouncerModel + } + + fun setShowingSoon(showingSoon: Boolean) { + _showingSoon.value = showingSoon + } + + fun setHide(hide: Boolean) { + _hide.value = hide + } + + fun setStartingToHide(startingToHide: Boolean) { + _startingToHide.value = startingToHide + } + + fun setOnDismissAction(bouncerCallbackActionsModel: BouncerCallbackActionsModel?) { + _onDismissAction.value = bouncerCallbackActionsModel + } + + fun setStartDisappearAnimation(runnable: Runnable?) { + _disappearAnimation.value = runnable + } + + fun setKeyguardPosition(keyguardPosition: Float) { + _keyguardPosition.value = keyguardPosition + } + + fun setResourceUpdateRequests(willUpdateResources: Boolean) { + _resourceUpdateRequests.value = willUpdateResources + } + + fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) { + _showMessage.value = bouncerShowMessageModel + } + + fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) { + _keyguardAuthenticated.value = keyguardAuthenticated + } + + fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) { + _isBackButtonEnabled.value = isBackButtonEnabled + } + + fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) { + _onScreenTurnedOff.value = onScreenTurnedOff + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt new file mode 100644 index 000000000000..10c7a3774e09 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 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.keyguard.domain.interactor + +import android.view.View +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.phone.KeyguardBouncer +import com.android.systemui.util.ListenerSet +import javax.inject.Inject + +/** Interactor to add and remove callbacks for the bouncer. */ +@SysUISingleton +class BouncerCallbackInteractor @Inject constructor() { + private var resetCallbacks = ListenerSet<KeyguardBouncer.KeyguardResetCallback>() + private var expansionCallbacks = ArrayList<KeyguardBouncer.BouncerExpansionCallback>() + /** Add a KeyguardResetCallback. */ + fun addKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) { + resetCallbacks.addIfAbsent(callback) + } + + /** Remove a KeyguardResetCallback. */ + fun removeKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) { + resetCallbacks.remove(callback) + } + + /** Adds a callback to listen to bouncer expansion updates. */ + fun addBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) { + if (!expansionCallbacks.contains(callback)) { + expansionCallbacks.add(callback) + } + } + + /** + * Removes a previously added callback. If the callback was never added, this method does + * nothing. + */ + fun removeBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) { + expansionCallbacks.remove(callback) + } + + /** Propagate fully shown to bouncer expansion callbacks. */ + fun dispatchFullyShown() { + for (callback in expansionCallbacks) { + callback.onFullyShown() + } + } + + /** Propagate starting to hide to bouncer expansion callbacks. */ + fun dispatchStartingToHide() { + for (callback in expansionCallbacks) { + callback.onStartingToHide() + } + } + + /** Propagate starting to show to bouncer expansion callbacks. */ + fun dispatchStartingToShow() { + for (callback in expansionCallbacks) { + callback.onStartingToShow() + } + } + + /** Propagate fully hidden to bouncer expansion callbacks. */ + fun dispatchFullyHidden() { + for (callback in expansionCallbacks) { + callback.onFullyHidden() + } + } + + /** Propagate expansion changes to bouncer expansion callbacks. */ + fun dispatchExpansionChanged(expansion: Float) { + for (callback in expansionCallbacks) { + callback.onExpansionChanged(expansion) + } + } + /** Propagate visibility changes to bouncer expansion callbacks. */ + fun dispatchVisibilityChanged(visibility: Int) { + for (callback in expansionCallbacks) { + callback.onVisibilityChanged(visibility == View.VISIBLE) + } + } + + /** Propagate keyguard reset. */ + fun dispatchReset() { + for (callback in resetCallbacks) { + callback.onKeyguardReset() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt new file mode 100644 index 000000000000..7d4db37c6b0f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2022 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.keyguard.domain.interactor + +import android.content.res.ColorStateList +import android.os.Handler +import android.os.Trace +import android.os.UserHandle +import android.os.UserManager +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.DejankUtils +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.keyguard.data.BouncerView +import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository +import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel +import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel +import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.shared.system.SysUiStatsLog +import com.android.systemui.statusbar.phone.KeyguardBouncer +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.policy.KeyguardStateController +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map + +/** Encapsulates business logic for interacting with the lock-screen bouncer. */ +@SysUISingleton +class BouncerInteractor +@Inject +constructor( + private val repository: KeyguardBouncerRepository, + private val bouncerView: BouncerView, + @Main private val mainHandler: Handler, + private val keyguardStateController: KeyguardStateController, + private val keyguardSecurityModel: KeyguardSecurityModel, + private val callbackInteractor: BouncerCallbackInteractor, + private val falsingCollector: FalsingCollector, + private val dismissCallbackRegistry: DismissCallbackRegistry, + keyguardBypassController: KeyguardBypassController, + keyguardUpdateMonitor: KeyguardUpdateMonitor, +) { + /** Whether we want to wait for face auth. */ + private val bouncerFaceDelay = + keyguardStateController.isFaceAuthEnabled && + !keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( + KeyguardUpdateMonitor.getCurrentUser() + ) && + !needsFullscreenBouncer() && + !keyguardUpdateMonitor.userNeedsStrongAuth() && + !keyguardBypassController.bypassEnabled + + /** Runnable to show the bouncer. */ + val showRunnable = Runnable { + repository.setVisible(true) + repository.setShow( + KeyguardBouncerModel( + promptReason = repository.bouncerPromptReason ?: 0, + errorMessage = repository.bouncerErrorMessage, + expansionAmount = repository.expansionAmount.value + ) + ) + repository.setShowingSoon(false) + } + + val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull() + val screenTurnedOff: Flow<Unit> = repository.onScreenTurnedOff.filter { it }.map {} + val show: Flow<KeyguardBouncerModel> = repository.show.filterNotNull() + val hide: Flow<Unit> = repository.hide.filter { it }.map {} + val startingToHide: Flow<Unit> = repository.startingToHide.filter { it }.map {} + val isVisible: Flow<Boolean> = repository.isVisible + val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull() + val expansionAmount: Flow<Float> = repository.expansionAmount + val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull() + val startingDisappearAnimation: Flow<Runnable> = + repository.startingDisappearAnimation.filterNotNull() + val onDismissAction: Flow<BouncerCallbackActionsModel> = + repository.onDismissAction.filterNotNull() + val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it } + val keyguardPosition: Flow<Float> = repository.keyguardPosition + + // TODO(b/243685699): Move isScrimmed logic to data layer. + // TODO(b/243695312): Encapsulate all of the show logic for the bouncer. + /** Show the bouncer if necessary and set the relevant states. */ + @JvmOverloads + fun show(isScrimmed: Boolean) { + // Reset some states as we show the bouncer. + repository.setShowMessage(null) + repository.setOnScreenTurnedOff(false) + repository.setKeyguardAuthenticated(null) + repository.setHide(false) + repository.setStartingToHide(false) + + val resumeBouncer = + (repository.isVisible.value || repository.showingSoon.value) && needsFullscreenBouncer() + + if (!resumeBouncer && repository.show.value != null) { + // If bouncer is visible, the bouncer is already showing. + return + } + + val keyguardUserId = KeyguardUpdateMonitor.getCurrentUser() + if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) { + // In split system user mode, we never unlock system user. + return + } + + Trace.beginSection("KeyguardBouncer#show") + repository.setScrimmed(isScrimmed) + if (isScrimmed) { + setExpansion(KeyguardBouncer.EXPANSION_VISIBLE) + } + + if (resumeBouncer) { + bouncerView.delegate?.resume() + // Bouncer is showing the next security screen and we just need to prompt a resume. + return + } + if (bouncerView.delegate?.showNextSecurityScreenOrFinish() == true) { + // Keyguard is done. + return + } + + repository.setShowingSoon(true) + if (bouncerFaceDelay) { + mainHandler.postDelayed(showRunnable, 1200L) + } else { + DejankUtils.postAfterTraversal(showRunnable) + } + keyguardStateController.notifyBouncerShowing(true) + callbackInteractor.dispatchStartingToShow() + + Trace.endSection() + } + + /** Sets the correct bouncer states to hide the bouncer. */ + fun hide() { + Trace.beginSection("KeyguardBouncer#hide") + if (isFullyShowing()) { + SysUiStatsLog.write( + SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, + SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN + ) + dismissCallbackRegistry.notifyDismissCancelled() + } + + falsingCollector.onBouncerHidden() + keyguardStateController.notifyBouncerShowing(false /* showing */) + cancelShowRunnable() + repository.setShowingSoon(false) + repository.setOnDismissAction(null) + repository.setVisible(false) + repository.setHide(true) + repository.setShow(null) + Trace.endSection() + } + + /** + * Sets the panel expansion which is calculated further upstream. Expansion is from 0f to 1f + * where 0f => showing and 1f => hiding + */ + fun setExpansion(expansion: Float) { + val oldExpansion = repository.expansionAmount.value + val expansionChanged = oldExpansion != expansion + if (repository.startingDisappearAnimation.value == null) { + repository.setExpansion(expansion) + } + + if ( + expansion == KeyguardBouncer.EXPANSION_VISIBLE && + oldExpansion != KeyguardBouncer.EXPANSION_VISIBLE + ) { + falsingCollector.onBouncerShown() + callbackInteractor.dispatchFullyShown() + } else if ( + expansion == KeyguardBouncer.EXPANSION_HIDDEN && + oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN + ) { + repository.setVisible(false) + repository.setShow(null) + falsingCollector.onBouncerHidden() + DejankUtils.postAfterTraversal { callbackInteractor.dispatchReset() } + callbackInteractor.dispatchFullyHidden() + } else if ( + expansion != KeyguardBouncer.EXPANSION_VISIBLE && + oldExpansion == KeyguardBouncer.EXPANSION_VISIBLE + ) { + callbackInteractor.dispatchStartingToHide() + repository.setStartingToHide(true) + } + if (expansionChanged) { + callbackInteractor.dispatchExpansionChanged(expansion) + } + } + + /** Set the initial keyguard message to show when bouncer is shown. */ + fun showMessage(message: String?, colorStateList: ColorStateList?) { + repository.setShowMessage(BouncerShowMessageModel(message, colorStateList)) + } + + /** + * Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is + * unlocked, we will run the onDismissAction. If the bouncer is existed before unlocking, we + * call cancelAction. + */ + fun setDismissAction( + onDismissAction: ActivityStarter.OnDismissAction?, + cancelAction: Runnable? + ) { + repository.setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction)) + } + + /** Update the resources of the views. */ + fun updateResources() { + repository.setResourceUpdateRequests(true) + } + + /** Tell the bouncer that keyguard is authenticated. */ + fun notifyKeyguardAuthenticated(strongAuth: Boolean) { + repository.setKeyguardAuthenticated(strongAuth) + } + + /** Tell the bouncer the screen has turned off. */ + fun onScreenTurnedOff() { + repository.setOnScreenTurnedOff(true) + } + + /** Update the position of the bouncer when showing. */ + fun setKeyguardPosition(position: Float) { + repository.setKeyguardPosition(position) + } + + /** Notifies that the state change was handled. */ + fun notifyKeyguardAuthenticatedHandled() { + repository.setKeyguardAuthenticated(null) + } + + /** Notify that view visibility has changed. */ + fun notifyBouncerVisibilityHasChanged(visibility: Int) { + callbackInteractor.dispatchVisibilityChanged(visibility) + } + + /** Notify that the resources have been updated */ + fun notifyUpdatedResources() { + repository.setResourceUpdateRequests(false) + } + + /** Set whether back button is enabled when on the bouncer screen. */ + fun setBackButtonEnabled(enabled: Boolean) { + repository.setIsBackButtonEnabled(enabled) + } + + /** Tell the bouncer to start the pre hide animation. */ + fun startDisappearAnimation(runnable: Runnable) { + val finishRunnable = Runnable { + repository.setStartDisappearAnimation(null) + runnable.run() + } + repository.setStartDisappearAnimation(finishRunnable) + } + + /** Returns whether bouncer is fully showing. */ + fun isFullyShowing(): Boolean { + return (repository.showingSoon.value || repository.isVisible.value) && + repository.expansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE && + repository.startingDisappearAnimation.value == null + } + + /** Returns whether bouncer is scrimmed. */ + fun isScrimmed(): Boolean { + return repository.isScrimmed.value + } + + /** If bouncer expansion is between 0f and 1f non-inclusive. */ + fun isInTransit(): Boolean { + return repository.showingSoon.value || + repository.expansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN && + repository.expansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE + } + + /** Return whether bouncer is animating away. */ + fun isAnimatingAway(): Boolean { + return repository.startingDisappearAnimation.value != null + } + + /** Return whether bouncer will dismiss with actions */ + fun willDismissWithAction(): Boolean { + return repository.onDismissAction.value?.onDismissAction != null + } + + /** Returns whether the bouncer should be full screen. */ + private fun needsFullscreenBouncer(): Boolean { + val mode: KeyguardSecurityModel.SecurityMode = + keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()) + return mode == KeyguardSecurityModel.SecurityMode.SimPin || + mode == KeyguardSecurityModel.SecurityMode.SimPuk + } + + /** Remove the show runnable from the main handler queue to improve performance. */ + private fun cancelShowRunnable() { + DejankUtils.removeCallbacks(showRunnable) + mainHandler.removeCallbacks(showRunnable) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerCallbackActionsModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerCallbackActionsModel.kt new file mode 100644 index 000000000000..81cf5b41ea71 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerCallbackActionsModel.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 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.keyguard.shared.model + +import com.android.systemui.plugins.ActivityStarter + +/** Encapsulates callbacks to be invoked by the bouncer logic. */ +// TODO(b/243683121): Move dismiss logic from view controllers +data class BouncerCallbackActionsModel( + val onDismissAction: ActivityStarter.OnDismissAction?, + val cancelAction: Runnable? +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt new file mode 100644 index 000000000000..05cdeaaa106d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 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.keyguard.shared.model + +import android.content.res.ColorStateList + +/** Show a keyguard message to the bouncer. */ +data class BouncerShowMessageModel(val message: String?, val colorStateList: ColorStateList?) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt new file mode 100644 index 000000000000..ad783da7f304 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 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.keyguard.shared.model + +/** Models the state of the lock-screen bouncer */ +data class KeyguardBouncerModel( + val promptReason: Int = 0, + val errorMessage: CharSequence? = null, + val expansionAmount: Float = 0f, +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt new file mode 100644 index 000000000000..df260148751c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2022 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.keyguard.ui.binder + +import android.view.KeyEvent +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.internal.policy.SystemBarUtils +import com.android.keyguard.KeyguardHostViewController +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.dagger.KeyguardBouncerComponent +import com.android.systemui.keyguard.data.BouncerViewDelegate +import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.launch + +/** Binds the bouncer container to its view model. */ +object KeyguardBouncerViewBinder { + @JvmStatic + fun bind( + view: ViewGroup, + viewModel: KeyguardBouncerViewModel, + componentFactory: KeyguardBouncerComponent.Factory + ) { + // Builds the KeyguardHostViewController from bouncer view group. + val hostViewController: KeyguardHostViewController = + componentFactory.create(view).keyguardHostViewController + hostViewController.init() + val delegate = + object : BouncerViewDelegate { + override fun isFullScreenBouncer(): Boolean { + val mode = hostViewController.currentSecurityMode + return mode == KeyguardSecurityModel.SecurityMode.SimPin || + mode == KeyguardSecurityModel.SecurityMode.SimPuk + } + + override fun shouldDismissOnMenuPressed(): Boolean { + return hostViewController.shouldEnableMenuKey() + } + + override fun interceptMediaKey(event: KeyEvent?): Boolean { + return hostViewController.interceptMediaKey(event) + } + + override fun dispatchBackKeyEventPreIme(): Boolean { + return hostViewController.dispatchBackKeyEventPreIme() + } + + override fun showNextSecurityScreenOrFinish(): Boolean { + return hostViewController.dismiss(KeyguardUpdateMonitor.getCurrentUser()) + } + + override fun resume() { + hostViewController.showPrimarySecurityScreen() + hostViewController.onResume() + } + } + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + try { + viewModel.setBouncerViewDelegate(delegate) + launch { + viewModel.show.collect { + hostViewController.showPrimarySecurityScreen() + hostViewController.appear( + SystemBarUtils.getStatusBarHeight(view.context) + ) + } + } + + launch { + viewModel.showPromptReason.collect { prompt -> + hostViewController.showPromptReason(prompt) + } + } + + launch { + viewModel.showBouncerErrorMessage.collect { errorMessage -> + hostViewController.showErrorMessage(errorMessage) + } + } + + launch { + viewModel.showWithFullExpansion.collect { model -> + hostViewController.resetSecurityContainer() + hostViewController.showPromptReason(model.promptReason) + hostViewController.onResume() + } + } + + launch { + viewModel.hide.collect { + hostViewController.cancelDismissAction() + hostViewController.cleanUp() + hostViewController.resetSecurityContainer() + } + } + + launch { + viewModel.startingToHide.collect { hostViewController.onStartingToHide() } + } + + launch { + viewModel.setDismissAction.collect { + hostViewController.setOnDismissAction( + it.onDismissAction, + it.cancelAction + ) + } + } + + launch { + viewModel.startDisappearAnimation.collect { + hostViewController.startDisappearAnimation(it) + } + } + + launch { + viewModel.bouncerExpansionAmount.collect { expansion -> + hostViewController.setExpansion(expansion) + } + } + + launch { + viewModel.bouncerExpansionAmount + .filter { it == EXPANSION_VISIBLE } + .collect { + hostViewController.onResume() + view.announceForAccessibility( + hostViewController.accessibilityTitleForCurrentMode + ) + } + } + + launch { + viewModel.isBouncerVisible.collect { isVisible -> + val visibility = if (isVisible) View.VISIBLE else View.INVISIBLE + view.visibility = visibility + hostViewController.onBouncerVisibilityChanged(visibility) + viewModel.notifyBouncerVisibilityHasChanged(visibility) + } + } + + launch { + viewModel.isBouncerVisible + .filter { !it } + .collect { + // Remove existing input for security reasons. + hostViewController.resetSecurityContainer() + } + } + + launch { + viewModel.keyguardPosition.collect { position -> + hostViewController.updateKeyguardPosition(position) + } + } + + launch { + viewModel.updateResources.collect { + hostViewController.updateResources() + viewModel.notifyUpdateResources() + } + } + + launch { + viewModel.bouncerShowMessage.collect { + hostViewController.showMessage(it.message, it.colorStateList) + } + } + + launch { + viewModel.keyguardAuthenticated.collect { + hostViewController.finish(it, KeyguardUpdateMonitor.getCurrentUser()) + viewModel.notifyKeyguardAuthenticated() + } + } + + launch { + viewModel + .observeOnIsBackButtonEnabled { view.systemUiVisibility } + .collect { view.systemUiVisibility = it } + } + + launch { + viewModel.screenTurnedOff.collect { + if (view.visibility == View.VISIBLE) { + hostViewController.onPause() + } + } + } + awaitCancellation() + } finally { + viewModel.setBouncerViewDelegate(null) + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt new file mode 100644 index 000000000000..9ad52117bfc6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2022 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.keyguard.ui.viewmodel + +import android.view.View +import com.android.systemui.keyguard.data.BouncerView +import com.android.systemui.keyguard.data.BouncerViewDelegate +import com.android.systemui.keyguard.domain.interactor.BouncerInteractor +import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel +import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel +import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel +import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map + +/** Models UI state for the lock screen bouncer; handles user input. */ +class KeyguardBouncerViewModel +@Inject +constructor( + private val view: BouncerView, + private val interactor: BouncerInteractor, +) { + /** Observe on bouncer expansion amount. */ + val bouncerExpansionAmount: Flow<Float> = interactor.expansionAmount + + /** Observe on bouncer visibility. */ + val isBouncerVisible: Flow<Boolean> = interactor.isVisible + + /** Observe whether bouncer is showing. */ + val show: Flow<KeyguardBouncerModel> = interactor.show + + /** Observe bouncer prompt when bouncer is showing. */ + val showPromptReason: Flow<Int> = interactor.show.map { it.promptReason } + + /** Observe bouncer error message when bouncer is showing. */ + val showBouncerErrorMessage: Flow<CharSequence> = + interactor.show.map { it.errorMessage }.filterNotNull() + + /** Observe visible expansion when bouncer is showing. */ + val showWithFullExpansion: Flow<KeyguardBouncerModel> = + interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE } + + /** Observe whether bouncer is hiding. */ + val hide: Flow<Unit> = interactor.hide + + /** Observe whether bouncer is starting to hide. */ + val startingToHide: Flow<Unit> = interactor.startingToHide + + /** Observe whether we want to set the dismiss action to the bouncer. */ + val setDismissAction: Flow<BouncerCallbackActionsModel> = interactor.onDismissAction + + /** Observe whether we want to start the disappear animation. */ + val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation + + /** Observe whether we want to update keyguard position. */ + val keyguardPosition: Flow<Float> = interactor.keyguardPosition + + /** Observe whether we want to update resources. */ + val updateResources: Flow<Boolean> = interactor.resourceUpdateRequests + + /** Observe whether we want to set a keyguard message when the bouncer shows. */ + val bouncerShowMessage: Flow<BouncerShowMessageModel> = interactor.showMessage + + /** Observe whether keyguard is authenticated already. */ + val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticated + + /** Observe whether screen is turned off. */ + val screenTurnedOff: Flow<Unit> = interactor.screenTurnedOff + + /** Notify that view visibility has changed. */ + fun notifyBouncerVisibilityHasChanged(visibility: Int) { + return interactor.notifyBouncerVisibilityHasChanged(visibility) + } + /** Observe whether we want to update resources. */ + fun notifyUpdateResources() { + interactor.notifyUpdatedResources() + } + + /** Notify that keyguard authenticated was handled */ + fun notifyKeyguardAuthenticated() { + interactor.notifyKeyguardAuthenticatedHandled() + } + + /** Observe whether back button is enabled. */ + fun observeOnIsBackButtonEnabled(systemUiVisibility: () -> Int): Flow<Int> { + return interactor.isBackButtonEnabled.map { enabled -> + var vis: Int = systemUiVisibility() + vis = + if (enabled) { + vis and View.STATUS_BAR_DISABLE_BACK.inv() + } else { + vis or View.STATUS_BAR_DISABLE_BACK + } + vis + } + } + + /** Set an abstraction that will hold reference to the ui delegate for the bouncer view. */ + fun setBouncerViewDelegate(delegate: BouncerViewDelegate?) { + view.delegate = delegate + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index e25f5dabe5a9..f8c6a5791839 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -1,5 +1,6 @@ package com.android.systemui.media +import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.res.ColorStateList @@ -945,6 +946,11 @@ class MediaCarouselController @Inject constructor( mediaManager.onSwipeToDismiss() } + fun getCurrentVisibleMediaContentIntent(): PendingIntent? { + return MediaPlayerData.playerKeys() + .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)?.data?.clickIntent + } + override fun dump(pw: PrintWriter, args: Array<out String>) { pw.apply { println("keysNeedRemoval: $keysNeedRemoval") diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java index dc1488eefc4e..53b4d434bfcb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java @@ -16,7 +16,7 @@ package com.android.systemui.media.dream; -import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION; +import static com.android.systemui.flags.Flags.DREAM_MEDIA_COMPLICATION; import android.content.Context; import android.util.Log; @@ -77,7 +77,7 @@ public class MediaDreamSentinel extends CoreStartable { public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey, @NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency, boolean isSsReactivated) { - if (!mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)) { + if (!mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java index 0ec4eef1e551..97476b2d1cde 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java @@ -16,9 +16,6 @@ package com.android.systemui.qs.tiles; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA; - import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; @@ -42,6 +39,7 @@ import com.android.systemui.qs.PseudoGridView; import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.qs.user.UserSwitchDialogController; import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.user.data.source.UserRecord; @@ -73,7 +71,8 @@ public class UserDetailView extends PseudoGridView { mAdapter.refresh(); } - public static class Adapter extends UserSwitcherController.BaseUserAdapter + /** Provides views for user detail items. */ + public static class Adapter extends BaseUserSwitcherAdapter implements OnClickListener { private final Context mContext; @@ -137,7 +136,7 @@ public class UserDetailView extends PseudoGridView { v.setActivated(item.isCurrent); v.setDisabledByAdmin(mController.isDisabledByAdmin(item)); v.setEnabled(item.isSwitchToEnabled); - v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA); + UserSwitcherController.setSelectableAlpha(v); if (item.isCurrent) { mCurrentUserView = v; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 8d74a09ab8b1..6be9bbbf4e0d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -31,10 +31,15 @@ import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.LockIconViewController; +import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dock.DockManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder; +import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationShadeDepthController; @@ -108,7 +113,10 @@ public class NotificationShadeWindowViewController { NotificationShadeWindowController controller, KeyguardUnlockAnimationController keyguardUnlockAnimationController, AmbientState ambientState, - PulsingGestureListener pulsingGestureListener + PulsingGestureListener pulsingGestureListener, + FeatureFlags featureFlags, + KeyguardBouncerViewModel keyguardBouncerViewModel, + KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory ) { mLockscreenShadeTransitionController = transitionController; mFalsingCollector = falsingCollector; @@ -130,6 +138,12 @@ public class NotificationShadeWindowViewController { // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); + if (featureFlags.isEnabled(Flags.MODERN_BOUNCER)) { + KeyguardBouncerViewBinder.bind( + mView.findViewById(R.id.keyguard_bouncer_container), + keyguardBouncerViewModel, + keyguardBouncerComponentFactory); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java deleted file mode 100644 index 4551807499ab..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2016 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; - -import android.content.Context; -import android.content.DialogInterface; - -import com.android.systemui.R; -import com.android.systemui.statusbar.phone.SystemUIDialog; -import com.android.systemui.statusbar.policy.UserSwitcherController; - -public class UserUtil { - public static void deleteUserWithPrompt(Context context, int userId, - UserSwitcherController userSwitcherController) { - new RemoveUserDialog(context, userId, userSwitcherController).show(); - } - - private final static class RemoveUserDialog extends SystemUIDialog implements - DialogInterface.OnClickListener { - - private final int mUserId; - private final UserSwitcherController mUserSwitcherController; - - public RemoveUserDialog(Context context, int userId, - UserSwitcherController userSwitcherController) { - super(context); - setTitle(R.string.user_remove_user_title); - setMessage(context.getString(R.string.user_remove_user_message)); - setButton(DialogInterface.BUTTON_NEUTRAL, - context.getString(android.R.string.cancel), this); - setButton(DialogInterface.BUTTON_POSITIVE, - context.getString(R.string.user_remove_user_remove), this); - setCanceledOnTouchOutside(false); - mUserId = userId; - mUserSwitcherController = userSwitcherController; - } - - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == BUTTON_NEUTRAL) { - cancel(); - } else { - dismiss(); - mUserSwitcherController.removeUserId(mUserId); - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index d380e9f03720..d61c51e76e86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -56,7 +56,9 @@ import javax.inject.Inject; /** * A class which manages the bouncer on the lockscreen. + * @deprecated Use KeyguardBouncerRepository */ +@Deprecated public class KeyguardBouncer { private static final String TAG = "KeyguardBouncer"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java index 4d6168989691..00c3e8fac0b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java @@ -33,6 +33,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.FooterActionsView; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.user.UserSwitchDialogController; +import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.user.UserSwitcherActivity; import com.android.systemui.util.ViewController; @@ -49,7 +50,7 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { private final ActivityStarter mActivityStarter; private final FeatureFlags mFeatureFlags; - private UserSwitcherController.BaseUserAdapter mUserListener; + private BaseUserSwitcherAdapter mUserListener; private final View.OnClickListener mOnClickListener = new View.OnClickListener() { @Override @@ -135,7 +136,7 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { final UserSwitcherController controller = mUserSwitcherController; if (controller != null) { - mUserListener = new UserSwitcherController.BaseUserAdapter(controller) { + mUserListener = new BaseUserSwitcherAdapter(controller) { @Override public void notifyDataSetChanged() { mView.refreshContentDescription(getCurrentUser()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 786714716596..e61794b0243e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -46,6 +46,7 @@ import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.KeyguardMessageAreaController; +import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; @@ -53,6 +54,12 @@ import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.data.BouncerView; +import com.android.systemui.keyguard.data.BouncerViewDelegate; +import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor; +import com.android.systemui.keyguard.domain.interactor.BouncerInteractor; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -123,6 +130,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Nullable private final FoldAodAnimationController mFoldAodAnimationController; private KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController; + private final BouncerCallbackInteractor mBouncerCallbackInteractor; + private final BouncerInteractor mBouncerInteractor; + private final BouncerViewDelegate mBouncerViewDelegate; private final Lazy<com.android.systemui.shade.ShadeController> mShadeController; private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() { @@ -197,7 +207,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private View mNotificationContainer; - protected KeyguardBouncer mBouncer; + @Nullable protected KeyguardBouncer mBouncer; protected boolean mShowing; protected boolean mOccluded; protected boolean mRemoteInputActive; @@ -223,6 +233,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private int mLastBiometricMode; private boolean mLastScreenOffAnimationPlaying; private float mQsExpansion; + private boolean mIsModernBouncerEnabled; private OnDismissAction mAfterKeyguardGoneAction; private Runnable mKeyguardGoneCancelAction; @@ -237,6 +248,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final DockManager mDockManager; private final KeyguardUpdateMonitor mKeyguardUpdateManager; private final LatencyTracker mLatencyTracker; + private final KeyguardSecurityModel mKeyguardSecurityModel; private KeyguardBypassController mBypassController; @Nullable private AlternateAuthInterceptor mAlternateAuthInterceptor; @@ -271,7 +283,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb KeyguardMessageAreaController.Factory keyguardMessageAreaFactory, Optional<SysUIUnfoldComponent> sysUIUnfoldComponent, Lazy<ShadeController> shadeController, - LatencyTracker latencyTracker) { + LatencyTracker latencyTracker, + KeyguardSecurityModel keyguardSecurityModel, + FeatureFlags featureFlags, + BouncerCallbackInteractor bouncerCallbackInteractor, + BouncerInteractor bouncerInteractor, + BouncerView bouncerView) { mContext = context; mViewMediatorCallback = callback; mLockPatternUtils = lockPatternUtils; @@ -288,8 +305,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardMessageAreaFactory = keyguardMessageAreaFactory; mShadeController = shadeController; mLatencyTracker = latencyTracker; + mKeyguardSecurityModel = keyguardSecurityModel; + mBouncerCallbackInteractor = bouncerCallbackInteractor; + mBouncerInteractor = bouncerInteractor; + mBouncerViewDelegate = bouncerView.getDelegate(); mFoldAodAnimationController = sysUIUnfoldComponent .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null); + mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER); } @Override @@ -303,7 +325,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBiometricUnlockController = biometricUnlockController; ViewGroup container = mCentralSurfaces.getBouncerContainer(); - mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback); + if (mIsModernBouncerEnabled) { + mBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback); + } else { + mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback); + } mNotificationPanelViewController = notificationPanelViewController; if (panelExpansionStateManager != null) { panelExpansionStateManager.addExpansionListener(this); @@ -377,29 +403,45 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (mDozing && !mPulsing) { return; } else if (mNotificationPanelViewController.isUnlockHintRunning()) { - mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); + if (mBouncer != null) { + mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); + } + mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) { // Don't expand to the bouncer. Instead transition back to the lock screen (see // CentralSurfaces#showBouncerOrLockScreenIfKeyguard) return; } else if (bouncerNeedsScrimming()) { - mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); + if (mBouncer != null) { + mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); + } + mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); } else if (mShowing && !hideBouncerOverDream) { if (!isWakeAndUnlocking() && !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER) && !mCentralSurfaces.isInLaunchTransition() && !isUnlockCollapsing()) { - mBouncer.setExpansion(fraction); + if (mBouncer != null) { + mBouncer.setExpansion(fraction); + } + mBouncerInteractor.setExpansion(fraction); } if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking && !mKeyguardStateController.canDismissLockScreen() - && !mBouncer.isShowing() && !mBouncer.isAnimatingAway()) { - mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */); + && !bouncerIsShowing() + && !bouncerIsAnimatingAway()) { + if (mBouncer != null) { + mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */); + } + mBouncerInteractor.show(/* isScrimmed= */false); } - } else if (!mShowing && mBouncer.inTransit()) { + } else if (!mShowing && isBouncerInTransit()) { // Keyguard is not visible anymore, but expansion animation was still running. // We need to hide the bouncer, otherwise it will be stuck in transit. - mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); + if (mBouncer != null) { + mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); + } + mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); } else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) { // Panel expanded while pulsing but didn't translate the bouncer (because we are // unlocked.) Let's simply wake-up to dismiss the lock screen. @@ -440,15 +482,20 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * {@link KeyguardBouncer#needsFullscreenBouncer()}. */ protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) { - if (mBouncer.needsFullscreenBouncer() && !mDozing) { + if (needsFullscreenBouncer() && !mDozing) { // The keyguard might be showing (already). So we need to hide it. mCentralSurfaces.hideKeyguard(); - mBouncer.show(true /* resetSecuritySelection */); + if (mBouncer != null) { + mBouncer.show(true /* resetSecuritySelection */); + } + mBouncerInteractor.show(true); } else { mCentralSurfaces.showKeyguard(); if (hideBouncerWhenShowing) { hideBouncer(false /* destroyView */); - mBouncer.prepare(); + if (mBouncer != null) { + mBouncer.prepare(); + } } } updateStates(); @@ -480,10 +527,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb */ @VisibleForTesting void hideBouncer(boolean destroyView) { - if (mBouncer == null) { - return; + if (mBouncer != null) { + mBouncer.hide(destroyView); } - mBouncer.hide(destroyView); + mBouncerInteractor.hide(); if (mShowing) { // If we were showing the bouncer and then aborting, we need to also clear out any // potential actions unless we actually unlocked. @@ -501,8 +548,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void showBouncer(boolean scrimmed) { resetAlternateAuth(false); - if (mShowing && !mBouncer.isShowing()) { - mBouncer.show(false /* resetSecuritySelection */, scrimmed); + if (mShowing && !isBouncerShowing()) { + if (mBouncer != null) { + mBouncer.show(false /* resetSecuritySelection */, scrimmed); + } + mBouncerInteractor.show(scrimmed); } updateStates(); } @@ -535,7 +585,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // instead of the bouncer. if (shouldShowAltAuth()) { if (!afterKeyguardGone) { - mBouncer.setDismissAction(mAfterKeyguardGoneAction, + if (mBouncer != null) { + mBouncer.setDismissAction(mAfterKeyguardGoneAction, + mKeyguardGoneCancelAction); + } + mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction, mKeyguardGoneCancelAction); mAfterKeyguardGoneAction = null; mKeyguardGoneCancelAction = null; @@ -549,12 +603,18 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (afterKeyguardGone) { // we'll handle the dismiss action after keyguard is gone, so just show the // bouncer - mBouncer.show(false /* resetSecuritySelection */); + mBouncerInteractor.show(/* isScrimmed= */true); + if (mBouncer != null) mBouncer.show(false /* resetSecuritySelection */); } else { // after authentication success, run dismiss action with the option to defer // hiding the keyguard based on the return value of the OnDismissAction - mBouncer.showWithDismissAction(mAfterKeyguardGoneAction, - mKeyguardGoneCancelAction); + mBouncerInteractor.setDismissAction( + mAfterKeyguardGoneAction, mKeyguardGoneCancelAction); + mBouncerInteractor.show(/* isScrimmed= */true); + if (mBouncer != null) { + mBouncer.showWithDismissAction(mAfterKeyguardGoneAction, + mKeyguardGoneCancelAction); + } // bouncer will handle the dismiss action, so we no longer need to track it here mAfterKeyguardGoneAction = null; mKeyguardGoneCancelAction = null; @@ -591,7 +651,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // Hide bouncer and quick-quick settings. if (mOccluded && !mDozing) { mCentralSurfaces.hideKeyguard(); - if (hideBouncerWhenShowing || mBouncer.needsFullscreenBouncer()) { + if (hideBouncerWhenShowing || needsFullscreenBouncer()) { hideBouncer(false /* destroyView */); } } else { @@ -655,7 +715,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void onFinishedGoingToSleep() { - mBouncer.onScreenTurnedOff(); + if (mBouncer != null) { + mBouncer.onScreenTurnedOff(); + } + mBouncerInteractor.onScreenTurnedOff(); } @Override @@ -746,7 +809,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // by a FLAG_DISMISS_KEYGUARD_ACTIVITY. reset(isOccluding /* hideBouncerWhenShowing*/); } - if (animate && !mOccluded && mShowing && !mBouncer.isShowing()) { + if (animate && !mOccluded && mShowing && !bouncerIsShowing()) { mCentralSurfaces.animateKeyguardUnoccluding(); } } @@ -762,8 +825,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void startPreHideAnimation(Runnable finishRunnable) { - if (mBouncer.isShowing()) { - mBouncer.startPreHideAnimation(finishRunnable); + if (bouncerIsShowing()) { + if (mBouncer != null) { + mBouncer.startPreHideAnimation(finishRunnable); + } + mBouncerInteractor.startDisappearAnimation(finishRunnable); mCentralSurfaces.onBouncerPreHideAnimation(); // We update the state (which will show the keyguard) only if an animation will run on @@ -873,8 +939,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public void onThemeChanged() { - boolean wasShowing = mBouncer.isShowing(); - boolean wasScrimmed = mBouncer.isScrimmed(); + if (mIsModernBouncerEnabled) { + updateResources(); + return; + } + boolean wasShowing = bouncerIsShowing(); + boolean wasScrimmed = bouncerIsScrimmed(); hideBouncer(true /* destroyView */); mBouncer.prepare(); @@ -924,7 +994,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * WARNING: This method might cause Binder calls. */ public boolean isSecure() { - return mBouncer.isSecure(); + if (mBouncer != null) { + return mBouncer.isSecure(); + } + + return mKeyguardSecurityModel.getSecurityMode( + KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None; } @Override @@ -941,10 +1016,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * @return whether the back press has been handled */ public boolean onBackPressed(boolean hideImmediately) { - if (mBouncer.isShowing()) { + if (bouncerIsShowing()) { mCentralSurfaces.endAffordanceLaunch(); // The second condition is for SIM card locked bouncer - if (mBouncer.isScrimmed() && !mBouncer.needsFullscreenBouncer()) { + if (bouncerIsScrimmed() + && !needsFullscreenBouncer()) { hideBouncer(false); updateStates(); } else { @@ -957,16 +1033,19 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public boolean isBouncerShowing() { - return mBouncer.isShowing() || isShowingAlternateAuth(); + return bouncerIsShowing() || isShowingAlternateAuth(); } @Override public boolean bouncerIsOrWillBeShowing() { - return isBouncerShowing() || mBouncer.inTransit(); + return isBouncerShowing() || isBouncerInTransit(); } public boolean isFullscreenBouncer() { - return mBouncer.isFullscreenBouncer(); + if (mBouncerViewDelegate != null) { + return mBouncerViewDelegate.isFullScreenBouncer(); + } + return mBouncer != null && mBouncer.isFullscreenBouncer(); } /** @@ -987,7 +1066,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private long getNavBarShowDelay() { if (mKeyguardStateController.isKeyguardFadingAway()) { return mKeyguardStateController.getKeyguardFadingAwayDelay(); - } else if (mBouncer.isShowing()) { + } else if (isBouncerShowing()) { return NAV_BAR_SHOW_DELAY_BOUNCER; } else { // No longer dozing, or remote input is active. No delay. @@ -1010,18 +1089,24 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb protected void updateStates() { boolean showing = mShowing; boolean occluded = mOccluded; - boolean bouncerShowing = mBouncer.isShowing(); + boolean bouncerShowing = bouncerIsShowing(); boolean bouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing(); - boolean bouncerDismissible = !mBouncer.isFullscreenBouncer(); + boolean bouncerDismissible = !isFullscreenBouncer(); boolean remoteInputActive = mRemoteInputActive; if ((bouncerDismissible || !showing || remoteInputActive) != (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive) || mFirstUpdate) { if (bouncerDismissible || !showing || remoteInputActive) { - mBouncer.setBackButtonEnabled(true); + if (mBouncer != null) { + mBouncer.setBackButtonEnabled(true); + } + mBouncerInteractor.setBackButtonEnabled(true); } else { - mBouncer.setBackButtonEnabled(false); + if (mBouncer != null) { + mBouncer.setBackButtonEnabled(false); + } + mBouncerInteractor.setBackButtonEnabled(false); } } @@ -1098,7 +1183,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb || mPulsing && !mIsDocked) && mGesturalNav; return (!keyguardShowing && !hideWhileDozing && !mScreenOffAnimationPlaying - || mBouncer.isShowing() || mRemoteInputActive || keyguardWithGestureNav + || bouncerIsShowing() + || mRemoteInputActive + || keyguardWithGestureNav || mGlobalActionsVisible); } @@ -1117,18 +1204,27 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public boolean shouldDismissOnMenuPressed() { - return mBouncer.shouldDismissOnMenuPressed(); + if (mBouncerViewDelegate != null) { + return mBouncerViewDelegate.shouldDismissOnMenuPressed(); + } + return mBouncer != null && mBouncer.shouldDismissOnMenuPressed(); } public boolean interceptMediaKey(KeyEvent event) { - return mBouncer.interceptMediaKey(event); + if (mBouncerViewDelegate != null) { + return mBouncerViewDelegate.interceptMediaKey(event); + } + return mBouncer != null && mBouncer.interceptMediaKey(event); } /** * @return true if the pre IME back event should be handled */ public boolean dispatchBackKeyEventPreIme() { - return mBouncer.dispatchBackKeyEventPreIme(); + if (mBouncerViewDelegate != null) { + return mBouncerViewDelegate.dispatchBackKeyEventPreIme(); + } + return mBouncer != null && mBouncer.dispatchBackKeyEventPreIme(); } public void readyForKeyguardDone() { @@ -1151,7 +1247,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public boolean isSecure(int userId) { - return mBouncer.isSecure() || mLockPatternUtils.isSecure(userId); + return isSecure() || mLockPatternUtils.isSecure(userId); } @Override @@ -1174,7 +1270,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * fingerprint. */ public void notifyKeyguardAuthenticated(boolean strongAuth) { - mBouncer.notifyKeyguardAuthenticated(strongAuth); + if (mBouncer != null) { + mBouncer.notifyKeyguardAuthenticated(strongAuth); + } + mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth); if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) { resetAlternateAuth(false); @@ -1189,7 +1288,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardMessageAreaController.setMessage(message); } } else { - mBouncer.showMessage(message, colorState); + if (mBouncer != null) { + mBouncer.showMessage(message, colorState); + } + mBouncerInteractor.showMessage(message, colorState); } } @@ -1222,9 +1324,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public boolean bouncerNeedsScrimming() { // When a dream overlay is active, scrimming will cause any expansion to immediately expand. return (mOccluded && !mDreamOverlayStateController.isOverlayActive()) - || mBouncer.willDismissWithAction() - || (mBouncer.isShowing() && mBouncer.isScrimmed()) - || mBouncer.isFullscreenBouncer(); + || bouncerWillDismissWithAction() + || (bouncerIsShowing() + && bouncerIsScrimmed()) + || isFullscreenBouncer(); } /** @@ -1236,6 +1339,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (mBouncer != null) { mBouncer.updateResources(); } + mBouncerInteractor.updateResources(); } public void dump(PrintWriter pw) { @@ -1289,6 +1393,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } + @Nullable public KeyguardBouncer getBouncer() { return mBouncer; } @@ -1320,6 +1425,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (mBouncer != null) { mBouncer.updateKeyguardPosition(x); } + + mBouncerInteractor.setKeyguardPosition(x); } private static class DismissWithActionRequest { @@ -1359,9 +1466,65 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * Returns if bouncer expansion is between 0 and 1 non-inclusive. */ public boolean isBouncerInTransit() { - if (mBouncer == null) return false; + if (mBouncer != null) { + return mBouncer.inTransit(); + } + + return mBouncerInteractor.isInTransit(); + } + + /** + * Returns if bouncer is showing + */ + public boolean bouncerIsShowing() { + if (mBouncer != null) { + return mBouncer.isShowing(); + } + + return mBouncerInteractor.isFullyShowing(); + } + + /** + * Returns if bouncer is scrimmed + */ + public boolean bouncerIsScrimmed() { + if (mBouncer != null) { + return mBouncer.isScrimmed(); + } + + return mBouncerInteractor.isScrimmed(); + } - return mBouncer.inTransit(); + /** + * Returns if bouncer is animating away + */ + public boolean bouncerIsAnimatingAway() { + if (mBouncer != null) { + return mBouncer.isAnimatingAway(); + } + + return mBouncerInteractor.isAnimatingAway(); + } + + /** + * Returns if bouncer will dismiss with action + */ + public boolean bouncerWillDismissWithAction() { + if (mBouncer != null) { + return mBouncer.willDismissWithAction(); + } + + return mBouncerInteractor.willDismissWithAction(); + } + + /** + * Returns if bouncer needs fullscreen bouncer. i.e. sim pin security method + */ + public boolean needsFullscreenBouncer() { + KeyguardSecurityModel.SecurityMode mode = mKeyguardSecurityModel.getSecurityMode( + KeyguardUpdateMonitor.getCurrentUser()); + return mode == KeyguardSecurityModel.SecurityMode.SimPin + || mode == KeyguardSecurityModel.SecurityMode.SimPuk; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt new file mode 100644 index 000000000000..5b2d69564585 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2022 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.policy + +import android.content.Context +import android.graphics.ColorFilter +import android.graphics.ColorMatrix +import android.graphics.ColorMatrixColorFilter +import android.graphics.drawable.Drawable +import android.os.UserHandle +import android.widget.BaseAdapter +import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower +import com.android.systemui.user.data.source.UserRecord +import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper.getUserRecordName +import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper.getUserSwitcherActionIconResourceId +import java.lang.ref.WeakReference + +/** Provides views for user switcher experiences. */ +abstract class BaseUserSwitcherAdapter +protected constructor( + protected val controller: UserSwitcherController, +) : BaseAdapter() { + + protected open val users: ArrayList<UserRecord> + get() = controller.users + + init { + controller.addAdapter(WeakReference(this)) + } + + override fun getCount(): Int { + return if (controller.isKeyguardShowing) { + users.count { !it.isRestricted } + } else { + users.size + } + } + + override fun getItem(position: Int): UserRecord { + return users[position] + } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + /** + * Notifies that a user item in the UI has been clicked. + * + * If the user switcher is hosted in a dialog, passing a non-null [dialogShower] will allow + * animation to and from the parent dialog. + */ + @JvmOverloads + fun onUserListItemClicked( + record: UserRecord, + dialogShower: DialogShower? = null, + ) { + controller.onUserListItemClicked(record, dialogShower) + } + + open fun getName(context: Context, item: UserRecord): String { + return getName(context, item, false) + } + + /** Returns the name for the given {@link UserRecord}. */ + open fun getName(context: Context, item: UserRecord, isTablet: Boolean): String { + return getUserRecordName( + context = context, + record = item, + isGuestUserAutoCreated = controller.isGuestUserAutoCreated, + isGuestUserResetting = controller.isGuestUserResetting, + isTablet = isTablet, + ) + } + + fun refresh() { + controller.refreshUsers(UserHandle.USER_NULL) + } + + companion object { + @JvmStatic + protected val disabledUserAvatarColorFilter: ColorFilter by lazy { + val matrix = ColorMatrix() + matrix.setSaturation(0f) // 0 - grayscale + ColorMatrixColorFilter(matrix) + } + + @JvmStatic + @JvmOverloads + protected fun getIconDrawable( + context: Context, + item: UserRecord, + isTablet: Boolean = false, + ): Drawable { + val iconRes = + getUserSwitcherActionIconResourceId( + item.isAddUser, + item.isGuest, + item.isAddSupervisedUser, + isTablet, + ) + return checkNotNull(context.getDrawable(iconRes)) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 163060814545..dc73d1f007c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -69,7 +69,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> private final Context mContext; private Resources mResources; private final UserSwitcherController mUserSwitcherController; - private UserSwitcherController.BaseUserAdapter mAdapter; + private BaseUserSwitcherAdapter mAdapter; private final KeyguardStateController mKeyguardStateController; private final FalsingManager mFalsingManager; protected final SysuiStatusBarStateController mStatusBarStateController; @@ -171,7 +171,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar); mUserAvatarViewWithBackground = mView.findViewById( R.id.kg_multi_user_avatar_with_background); - mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) { + mAdapter = new BaseUserSwitcherAdapter(mUserSwitcherController) { @Override public View getView(int position, View convertView, ViewGroup parent) { return null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index e2f5734cb4f9..0995a00533a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -16,9 +16,6 @@ package com.android.systemui.statusbar.policy; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -232,14 +229,8 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } /** - * See: - * - * <ul> - * <li>{@link com.android.internal.R.bool.config_expandLockScreenUserSwitcher}</li> - * <li>{@link UserSwitcherController.SIMPLE_USER_SWITCHER_GLOBAL_SETTING}</li> - * </ul> + * Returns {@code true} if the user switcher should be open by default on the lock screen. * - * @return true if the user switcher should be open by default on the lock screen. * @see android.os.UserManager#isUserSwitcherEnabled() */ public boolean isSimpleUserSwitcher() { @@ -436,7 +427,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } static class KeyguardUserAdapter extends - UserSwitcherController.BaseUserAdapter implements View.OnClickListener { + BaseUserSwitcherAdapter implements View.OnClickListener { private final Context mContext; private final Resources mResources; @@ -514,9 +505,9 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS v.bind(name, drawable, item.info.id); } v.setActivated(item.isCurrent); - v.setDisabledByAdmin(mController.isDisabledByAdmin(item)); + v.setDisabledByAdmin(getController().isDisabledByAdmin(item)); v.setEnabled(item.isSwitchToEnabled); - v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA); + UserSwitcherController.setSelectableAlpha(v); if (item.isCurrent) { mCurrentUserView = v; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt new file mode 100644 index 000000000000..843c2329092c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2022 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.policy + +import android.annotation.UserIdInt +import android.content.Intent +import android.view.View +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin +import com.android.systemui.Dumpable +import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower +import com.android.systemui.user.data.source.UserRecord +import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper +import java.lang.ref.WeakReference +import kotlinx.coroutines.flow.Flow + +/** Defines interface for a class that provides user switching functionality and state. */ +interface UserSwitcherController : Dumpable { + + /** The current list of [UserRecord]. */ + val users: ArrayList<UserRecord> + + /** Whether the user switcher experience should use the simple experience. */ + val isSimpleUserSwitcher: Boolean + + /** Require a view for jank detection */ + fun init(view: View) + + /** The [UserRecord] of the current user or `null` when none. */ + val currentUserRecord: UserRecord? + + /** The name of the current user of the device or `null`, when none is selected. */ + val currentUserName: String? + + /** + * Notifies that a user has been selected. + * + * This will trigger the right user journeys to create a guest user, switch users, and/or + * navigate to the correct destination. + * + * If a user with the given ID is not found, this method is a no-op. + * + * @param userId The ID of the user to switch to. + * @param dialogShower An optional [DialogShower] in case we need to show dialogs. + */ + fun onUserSelected(userId: Int, dialogShower: DialogShower?) + + /** Whether it is allowed to add users while the device is locked. */ + val isAddUsersFromLockScreenEnabled: Flow<Boolean> + + /** Whether the guest user is configured to always be present on the device. */ + val isGuestUserAutoCreated: Boolean + + /** Whether the guest user is currently being reset. */ + val isGuestUserResetting: Boolean + + /** Creates and switches to the guest user. */ + fun createAndSwitchToGuestUser(dialogShower: DialogShower?) + + /** Shows the add user dialog. */ + fun showAddUserDialog(dialogShower: DialogShower?) + + /** Starts an activity to add a supervised user to the device. */ + fun startSupervisedUserActivity() + + /** Notifies when the display density or font scale has changed. */ + fun onDensityOrFontScaleChanged() + + /** Registers an adapter to notify when the users change. */ + fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) + + /** Notifies the item for a user has been clicked. */ + fun onUserListItemClicked(record: UserRecord, dialogShower: DialogShower?) + + /** + * Removes guest user and switches to target user. The guest must be the current user and its id + * must be `guestUserId`. + * + * If `targetUserId` is `UserHandle.USER_NULL`, then create a new guest user in the foreground, + * and immediately switch to it. This is used for wiping the current guest and replacing it with + * a new one. + * + * If `targetUserId` is specified, then remove the guest in the background while switching to + * `targetUserId`. + * + * If device is configured with `config_guestUserAutoCreated`, then after guest user is removed, + * a new one is created in the background. This has no effect if `targetUserId` is + * `UserHandle.USER_NULL`. + * + * @param guestUserId id of the guest user to remove + * @param targetUserId id of the user to switch to after guest is removed. If + * `UserHandle.USER_NULL`, then switch immediately to the newly created guest user. + */ + fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int) + + /** + * Exits guest user and switches to previous non-guest user. The guest must be the current user. + * + * @param guestUserId user id of the guest user to exit + * @param targetUserId user id of the guest user to exit, set to UserHandle#USER_NULL when + * target user id is not known + * @param forceRemoveGuestOnExit true: remove guest before switching user, false: remove guest + * only if its ephemeral, else keep guest + */ + fun exitGuestUser( + @UserIdInt guestUserId: Int, + @UserIdInt targetUserId: Int, + forceRemoveGuestOnExit: Boolean + ) + + /** + * Guarantee guest is present only if the device is provisioned. Otherwise, create a content + * observer to wait until the device is provisioned, then schedule the guest creation. + */ + fun schedulePostBootGuestCreation() + + /** Whether keyguard is showing. */ + val isKeyguardShowing: Boolean + + /** Returns the [EnforcedAdmin] for the given record, or `null` if there isn't one. */ + fun getEnforcedAdmin(record: UserRecord): EnforcedAdmin? + + /** Returns `true` if the given record is disabled by the admin; `false` otherwise. */ + fun isDisabledByAdmin(record: UserRecord): Boolean + + /** Starts an activity with the given [Intent]. */ + fun startActivity(intent: Intent) + + /** + * Refreshes users from UserManager. + * + * The pictures are only loaded if they have not been loaded yet. + * + * @param forcePictureLoadForId forces the picture of the given user to be reloaded. + */ + fun refreshUsers(forcePictureLoadForId: Int) + + /** Adds a subscriber to when user switches. */ + fun addUserSwitchCallback(callback: UserSwitchCallback) + + /** Removes a previously-added subscriber. */ + fun removeUserSwitchCallback(callback: UserSwitchCallback) + + /** Defines interface for classes that can be called back when the user is switched. */ + fun interface UserSwitchCallback { + /** Notifies that the user has switched. */ + fun onUserSwitched() + } + + companion object { + /** Alpha value to apply to a user view in the user switcher when it's selectable. */ + private const val ENABLED_ALPHA = + LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA + + /** Alpha value to apply to a user view in the user switcher when it's not selectable. */ + private const val DISABLED_ALPHA = + LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA + + @JvmStatic + fun setSelectableAlpha(view: View) { + view.alpha = + if (view.isEnabled) { + ENABLED_ALPHA + } else { + DISABLED_ALPHA + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt new file mode 100644 index 000000000000..12834f68c3b7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2022 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.policy + +import android.content.Intent +import android.view.View +import com.android.settingslib.RestrictedLockUtils +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.qs.user.UserSwitchDialogController +import com.android.systemui.user.data.source.UserRecord +import dagger.Lazy +import java.io.PrintWriter +import java.lang.ref.WeakReference +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +/** Implementation of [UserSwitcherController]. */ +class UserSwitcherControllerImpl +@Inject +constructor( + private val flags: FeatureFlags, + @Suppress("DEPRECATION") private val oldImpl: Lazy<UserSwitcherControllerOldImpl>, +) : UserSwitcherController { + + private val isNewImpl: Boolean + get() = flags.isEnabled(Flags.REFACTORED_USER_SWITCHER_CONTROLLER) + private val _oldImpl: UserSwitcherControllerOldImpl + get() = oldImpl.get() + + private fun notYetImplemented(): Nothing { + error("Not yet implemented!") + } + + override val users: ArrayList<UserRecord> + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.users + } + + override val isSimpleUserSwitcher: Boolean + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isSimpleUserSwitcher + } + + override fun init(view: View) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.init(view) + } + } + + override val currentUserRecord: UserRecord? + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.currentUserRecord + } + + override val currentUserName: String? + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.currentUserName + } + + override fun onUserSelected( + userId: Int, + dialogShower: UserSwitchDialogController.DialogShower? + ) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.onUserSelected(userId, dialogShower) + } + } + + override val isAddUsersFromLockScreenEnabled: Flow<Boolean> + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isAddUsersFromLockScreenEnabled + } + + override val isGuestUserAutoCreated: Boolean + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isGuestUserAutoCreated + } + + override val isGuestUserResetting: Boolean + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isGuestUserResetting + } + + override fun createAndSwitchToGuestUser( + dialogShower: UserSwitchDialogController.DialogShower?, + ) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.createAndSwitchToGuestUser(dialogShower) + } + } + + override fun showAddUserDialog(dialogShower: UserSwitchDialogController.DialogShower?) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.showAddUserDialog(dialogShower) + } + } + + override fun startSupervisedUserActivity() { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.startSupervisedUserActivity() + } + } + + override fun onDensityOrFontScaleChanged() { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.onDensityOrFontScaleChanged() + } + } + + override fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.addAdapter(adapter) + } + } + + override fun onUserListItemClicked( + record: UserRecord, + dialogShower: UserSwitchDialogController.DialogShower?, + ) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.onUserListItemClicked(record, dialogShower) + } + } + + override fun removeGuestUser(guestUserId: Int, targetUserId: Int) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.removeGuestUser(guestUserId, targetUserId) + } + } + + override fun exitGuestUser( + guestUserId: Int, + targetUserId: Int, + forceRemoveGuestOnExit: Boolean + ) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit) + } + } + + override fun schedulePostBootGuestCreation() { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.schedulePostBootGuestCreation() + } + } + + override val isKeyguardShowing: Boolean + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isKeyguardShowing + } + + override fun getEnforcedAdmin(record: UserRecord): RestrictedLockUtils.EnforcedAdmin? { + return if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.getEnforcedAdmin(record) + } + } + + override fun isDisabledByAdmin(record: UserRecord): Boolean { + return if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isDisabledByAdmin(record) + } + } + + override fun startActivity(intent: Intent) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.startActivity(intent) + } + } + + override fun refreshUsers(forcePictureLoadForId: Int) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.refreshUsers(forcePictureLoadForId) + } + } + + override fun addUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.addUserSwitchCallback(callback) + } + } + + override fun removeUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.removeUserSwitchCallback(callback) + } + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.dump(pw, args) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java index 1d5b88e7dee0..d365aa6f952d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java @@ -11,9 +11,8 @@ * 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 + * limitations under the License. */ - package com.android.systemui.statusbar.policy; import static android.os.UserManager.SWITCHABILITY_STATUS_OK; @@ -34,10 +33,6 @@ import android.content.IntentFilter; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.graphics.Bitmap; -import android.graphics.ColorFilter; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; @@ -51,7 +46,6 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.View; import android.view.WindowManagerGlobal; -import android.widget.BaseAdapter; import android.widget.Toast; import androidx.annotation.Nullable; @@ -63,7 +57,6 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.util.LatencyTracker; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.users.UserCreatingDialog; -import com.android.systemui.Dumpable; import com.android.systemui.GuestResetOrExitSessionReceiver; import com.android.systemui.GuestResumeSessionReceiver; import com.android.systemui.R; @@ -86,7 +79,6 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.CreateUserActivity; import com.android.systemui.user.data.source.UserRecord; -import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; @@ -106,15 +98,14 @@ import kotlinx.coroutines.flow.MutableStateFlow; import kotlinx.coroutines.flow.StateFlowKt; /** - * Keeps a list of all users on the device for user switching. + * Old implementation. Keeps a list of all users on the device for user switching. + * + * @deprecated This is the old implementation. Please depend on {@link UserSwitcherController} + * instead. */ +@Deprecated @SysUISingleton -public class UserSwitcherController implements Dumpable { - - public static final float USER_SWITCH_ENABLED_ALPHA = - LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA; - public static final float USER_SWITCH_DISABLED_ALPHA = - LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA; +public class UserSwitcherControllerOldImpl implements UserSwitcherController { private static final String TAG = "UserSwitcherController"; private static final boolean DEBUG = false; @@ -123,7 +114,7 @@ public class UserSwitcherController implements Dumpable { private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000; private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; - private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000l; + private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000L; private static final String INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user"; private static final String INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode"; @@ -132,7 +123,7 @@ public class UserSwitcherController implements Dumpable { protected final UserTracker mUserTracker; protected final UserManager mUserManager; private final ContentObserver mSettingsObserver; - private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>(); + private final ArrayList<WeakReference<BaseUserSwitcherAdapter>> mAdapters = new ArrayList<>(); @VisibleForTesting final GuestResumeSessionReceiver mGuestResumeSessionReceiver; @VisibleForTesting @@ -158,7 +149,6 @@ public class UserSwitcherController implements Dumpable { @VisibleForTesting Dialog mAddUserDialog; private int mLastNonGuestUser = UserHandle.USER_SYSTEM; - private boolean mResumeUserOnGuestLogout = true; private boolean mSimpleUserSwitcher; // When false, there won't be any visual affordance to add a new user from the keyguard even if // the user is unlocked @@ -187,7 +177,8 @@ public class UserSwitcherController implements Dumpable { Collections.synchronizedList(new ArrayList<>()); @Inject - public UserSwitcherController(Context context, + public UserSwitcherControllerOldImpl( + Context context, IActivityManager activityManager, UserManager userManager, UserTracker userTracker, @@ -303,16 +294,10 @@ public class UserSwitcherController implements Dumpable { refreshUsers(UserHandle.USER_NULL); } - /** - * Refreshes users from UserManager. - * - * The pictures are only loaded if they have not been loaded yet. - * - * @param forcePictureLoadForId forces the picture of the given user to be reloaded. - */ + @Override @SuppressWarnings("unchecked") - private void refreshUsers(int forcePictureLoadForId) { - if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId+")"); + public void refreshUsers(int forcePictureLoadForId) { + if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId + ")"); if (forcePictureLoadForId != UserHandle.USER_NULL) { mForcePictureLoadForUserId.put(forcePictureLoadForId, true); } @@ -323,8 +308,8 @@ public class UserSwitcherController implements Dumpable { boolean forceAllUsers = mForcePictureLoadForUserId.get(UserHandle.USER_ALL); SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size()); - final int N = mUsers.size(); - for (int i = 0; i < N; i++) { + final int userCount = mUsers.size(); + for (int i = 0; i < userCount; i++) { UserRecord r = mUsers.get(i); if (r == null || r.picture == null || r.info == null || forceAllUsers || mForcePictureLoadForUserId.get(r.info.id)) { @@ -431,38 +416,41 @@ public class UserSwitcherController implements Dumpable { }); } - boolean systemCanCreateUsers() { + private boolean systemCanCreateUsers() { return !mUserManager.hasBaseUserRestriction( UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM); } - boolean currentUserCanCreateUsers() { + private boolean currentUserCanCreateUsers() { UserInfo currentUser = mUserTracker.getUserInfo(); return currentUser != null && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM) && systemCanCreateUsers(); } - boolean anyoneCanCreateUsers() { + private boolean anyoneCanCreateUsers() { return systemCanCreateUsers() && mAddUsersFromLockScreen.getValue(); } + @VisibleForTesting boolean canCreateGuest(boolean hasExistingGuest) { return mUserSwitcherEnabled && (currentUserCanCreateUsers() || anyoneCanCreateUsers()) && !hasExistingGuest; } + @VisibleForTesting boolean canCreateUser() { return mUserSwitcherEnabled && (currentUserCanCreateUsers() || anyoneCanCreateUsers()) && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY); } - boolean createIsRestricted() { + private boolean createIsRestricted() { return !mAddUsersFromLockScreen.getValue(); } + @VisibleForTesting boolean canCreateSupervisedUser() { return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser(); } @@ -476,7 +464,7 @@ public class UserSwitcherController implements Dumpable { private void notifyAdapters() { for (int i = mAdapters.size() - 1; i >= 0; i--) { - BaseUserAdapter adapter = mAdapters.get(i).get(); + BaseUserSwitcherAdapter adapter = mAdapters.get(i).get(); if (adapter != null) { adapter.notifyDataSetChanged(); } else { @@ -485,37 +473,20 @@ public class UserSwitcherController implements Dumpable { } } + @Override public boolean isSimpleUserSwitcher() { return mSimpleUserSwitcher; } - public void setResumeUserOnGuestLogout(boolean resume) { - mResumeUserOnGuestLogout = resume; - } - /** * Returns whether the current user is a system user. */ - public boolean isSystemUser() { + @VisibleForTesting + boolean isSystemUser() { return mUserTracker.getUserId() == UserHandle.USER_SYSTEM; } - public void removeUserId(int userId) { - if (userId == UserHandle.USER_SYSTEM) { - Log.w(TAG, "User " + userId + " could not removed."); - return; - } - if (mUserTracker.getUserId() == userId) { - switchToUserId(UserHandle.USER_SYSTEM); - } - if (mUserManager.removeUser(userId)) { - refreshUsers(UserHandle.USER_NULL); - } - } - - /** - * @return UserRecord for the current user - */ + @Override public @Nullable UserRecord getCurrentUserRecord() { for (int i = 0; i < mUsers.size(); ++i) { UserRecord userRecord = mUsers.get(i); @@ -526,17 +497,7 @@ public class UserSwitcherController implements Dumpable { return null; } - /** - * Notifies that a user has been selected. - * - * <p>This will trigger the right user journeys to create a guest user, switch users, and/or - * navigate to the correct destination. - * - * <p>If a user with the given ID is not found, this method is a no-op. - * - * @param userId The ID of the user to switch to. - * @param dialogShower An optional {@link DialogShower} in case we need to show dialogs. - */ + @Override public void onUserSelected(int userId, @Nullable DialogShower dialogShower) { UserRecord userRecord = mUsers.stream() .filter(x -> x.resolveId() == userId) @@ -549,23 +510,23 @@ public class UserSwitcherController implements Dumpable { onUserListItemClicked(userRecord, dialogShower); } - /** Whether it is allowed to add users while the device is locked. */ - public Flow<Boolean> getAddUsersFromLockScreen() { + @Override + public Flow<Boolean> isAddUsersFromLockScreenEnabled() { return mAddUsersFromLockScreen; } - /** Returns {@code true} if the guest user is configured to always be present on the device. */ + @Override public boolean isGuestUserAutoCreated() { return mGuestUserAutoCreated; } - /** Returns {@code true} if the guest user is currently being reset. */ + @Override public boolean isGuestUserResetting() { return mGuestIsResetting.get(); } - @VisibleForTesting - void onUserListItemClicked(UserRecord record, DialogShower dialogShower) { + @Override + public void onUserListItemClicked(UserRecord record, DialogShower dialogShower) { if (record.isGuest && record.info == null) { createAndSwitchToGuestUser(dialogShower); } else if (record.isAddUser) { @@ -604,7 +565,7 @@ public class UserSwitcherController implements Dumpable { switchToUserId(id); } - protected void switchToUserId(int id) { + private void switchToUserId(int id) { try { if (mView != null) { mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder @@ -621,7 +582,7 @@ public class UserSwitcherController implements Dumpable { private void showExitGuestDialog(int id, boolean isGuestEphemeral, DialogShower dialogShower) { int newId = UserHandle.USER_SYSTEM; - if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) { + if (mLastNonGuestUser != UserHandle.USER_SYSTEM) { UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser); if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) { newId = info.id; @@ -645,9 +606,7 @@ public class UserSwitcherController implements Dumpable { } } - /** - * Creates and switches to the guest user. - */ + @Override public void createAndSwitchToGuestUser(@Nullable DialogShower dialogShower) { createGuestAsync(guestId -> { // guestId may be USER_NULL if we haven't reloaded the user list yet. @@ -658,9 +617,7 @@ public class UserSwitcherController implements Dumpable { }); } - /** - * Shows the add user dialog. - */ + @Override public void showAddUserDialog(@Nullable DialogShower dialogShower) { if (mAddUserDialog != null && mAddUserDialog.isShowing()) { mAddUserDialog.cancel(); @@ -677,9 +634,7 @@ public class UserSwitcherController implements Dumpable { } } - /** - * Starts an activity to add a supervised user to the device. - */ + @Override public void startSupervisedUserActivity() { final Intent intent = new Intent() .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER) @@ -711,7 +666,7 @@ public class UserSwitcherController implements Dumpable { public void onReceive(Context context, Intent intent) { if (DEBUG) { Log.v(TAG, "Broadcast: a=" + intent.getAction() - + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)); + + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)); } boolean unpauseRefreshUsers = false; @@ -725,8 +680,8 @@ public class UserSwitcherController implements Dumpable { final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); final UserInfo userInfo = mUserManager.getUserInfo(currentId); - final int N = mUsers.size(); - for (int i = 0; i < N; i++) { + final int userCount = mUsers.size(); + for (int i = 0; i < userCount; i++) { UserRecord record = mUsers.get(i); if (record.info == null) continue; boolean shouldBeCurrent = record.info.id == currentId; @@ -805,7 +760,7 @@ public class UserSwitcherController implements Dumpable { pw.println("mGuestUserAutoCreated=" + mGuestUserAutoCreated); } - /** Returns the name of the current user of the phone. */ + @Override public String getCurrentUserName() { if (mUsers.isEmpty()) return null; UserRecord item = mUsers.stream().filter(x -> x.isCurrent).findFirst().orElse(null); @@ -814,40 +769,22 @@ public class UserSwitcherController implements Dumpable { return item.info.name; } + @Override public void onDensityOrFontScaleChanged() { refreshUsers(UserHandle.USER_ALL); } - @VisibleForTesting - public void addAdapter(WeakReference<BaseUserAdapter> adapter) { + @Override + public void addAdapter(WeakReference<BaseUserSwitcherAdapter> adapter) { mAdapters.add(adapter); } - @VisibleForTesting + @Override public ArrayList<UserRecord> getUsers() { return mUsers; } - /** - * Removes guest user and switches to target user. The guest must be the current user and its id - * must be {@code guestUserId}. - * - * <p>If {@code targetUserId} is {@link UserHandle#USER_NULL}, then create a new guest user in - * the foreground, and immediately switch to it. This is used for wiping the current guest and - * replacing it with a new one. - * - * <p>If {@code targetUserId} is specified, then remove the guest in the background while - * switching to {@code targetUserId}. - * - * <p>If device is configured with {@link - * com.android.internal.R.bool.config_guestUserAutoCreated}, then after guest user is removed, a - * new one is created in the background. This has no effect if {@code targetUserId} is {@link - * UserHandle#USER_NULL}. - * - * @param guestUserId id of the guest user to remove - * @param targetUserId id of the user to switch to after guest is removed. If {@link - * UserHandle#USER_NULL}, then switch immediately to the newly created guest user. - */ + @Override public void removeGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId) { UserInfo currentUser = mUserTracker.getUserInfo(); if (currentUser.id != guestUserId) { @@ -894,18 +831,9 @@ public class UserSwitcherController implements Dumpable { } } - /** - * Exits guest user and switches to previous non-guest user. The guest must be the current - * user. - * - * @param guestUserId user id of the guest user to exit - * @param targetUserId user id of the guest user to exit, set to UserHandle#USER_NULL when - * target user id is not known - * @param forceRemoveGuestOnExit true: remove guest before switching user, - * false: remove guest only if its ephemeral, else keep guest - */ + @Override public void exitGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId, - boolean forceRemoveGuestOnExit) { + boolean forceRemoveGuestOnExit) { UserInfo currentUser = mUserTracker.getUserInfo(); if (currentUser.id != guestUserId) { Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")" @@ -921,7 +849,7 @@ public class UserSwitcherController implements Dumpable { int newUserId = UserHandle.USER_SYSTEM; if (targetUserId == UserHandle.USER_NULL) { // when target user is not specified switch to last non guest user - if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) { + if (mLastNonGuestUser != UserHandle.USER_SYSTEM) { UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser); if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) { newUserId = info.id; @@ -959,10 +887,7 @@ public class UserSwitcherController implements Dumpable { } - /** - * Guarantee guest is present only if the device is provisioned. Otherwise, create a content - * observer to wait until the device is provisioned, then schedule the guest creation. - */ + @Override public void schedulePostBootGuestCreation() { if (isDeviceAllowedToAddGuest()) { guaranteeGuestPresent(); @@ -1014,7 +939,7 @@ public class UserSwitcherController implements Dumpable { * @return The multi-user user ID of the newly created guest user, or * {@link UserHandle#USER_NULL} if the guest couldn't be created. */ - public @UserIdInt int createGuest() { + private @UserIdInt int createGuest() { UserInfo guest; try { guest = mUserManager.createGuest(mContext); @@ -1029,135 +954,27 @@ public class UserSwitcherController implements Dumpable { return guest.id; } - /** - * Require a view for jank detection - */ + @Override public void init(View view) { mView = view; } - @VisibleForTesting - public KeyguardStateController getKeyguardStateController() { - return mKeyguardStateController; + @Override + public boolean isKeyguardShowing() { + return mKeyguardStateController.isShowing(); } - /** - * Returns the {@link EnforcedAdmin} for the given record, or {@code null} if there isn't one. - */ + @Override @Nullable public EnforcedAdmin getEnforcedAdmin(UserRecord record) { return mEnforcedAdminByUserRecord.get(record); } - /** - * Returns {@code true} if the given record is disabled by the admin; {@code false} otherwise. - */ + @Override public boolean isDisabledByAdmin(UserRecord record) { return mDisabledByAdmin.contains(record); } - public static abstract class BaseUserAdapter extends BaseAdapter { - - final UserSwitcherController mController; - private final KeyguardStateController mKeyguardStateController; - - protected BaseUserAdapter(UserSwitcherController controller) { - mController = controller; - mKeyguardStateController = controller.getKeyguardStateController(); - controller.addAdapter(new WeakReference<>(this)); - } - - protected ArrayList<UserRecord> getUsers() { - return mController.getUsers(); - } - - public int getUserCount() { - return countUsers(false); - } - - @Override - public int getCount() { - return countUsers(true); - } - - private int countUsers(boolean includeGuest) { - boolean keyguardShowing = mKeyguardStateController.isShowing(); - final int userSize = getUsers().size(); - int count = 0; - for (int i = 0; i < userSize; i++) { - if (getUsers().get(i).isGuest && !includeGuest) { - continue; - } - if (getUsers().get(i).isRestricted && keyguardShowing) { - break; - } - count++; - } - return count; - } - - @Override - public UserRecord getItem(int position) { - return getUsers().get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - /** - * It handles click events on user list items. - * - * If the user switcher is hosted in a dialog, passing a non-null {@link DialogShower} - * will allow animation to and from the parent dialog. - * - */ - public void onUserListItemClicked(UserRecord record, @Nullable DialogShower dialogShower) { - mController.onUserListItemClicked(record, dialogShower); - } - - public void onUserListItemClicked(UserRecord record) { - onUserListItemClicked(record, null); - } - - public String getName(Context context, UserRecord item) { - return getName(context, item, false); - } - - /** - * Returns the name for the given {@link UserRecord}. - */ - public String getName(Context context, UserRecord item, boolean isTablet) { - return LegacyUserUiHelper.getUserRecordName( - context, - item, - mController.isGuestUserAutoCreated(), - mController.isGuestUserResetting(), - isTablet); - } - - protected static ColorFilter getDisabledUserAvatarColorFilter() { - ColorMatrix matrix = new ColorMatrix(); - matrix.setSaturation(0f); // 0 - grayscale - return new ColorMatrixColorFilter(matrix); - } - - protected static Drawable getIconDrawable(Context context, UserRecord item) { - return getIconDrawable(context, item, false); - } - protected static Drawable getIconDrawable(Context context, UserRecord item, - boolean isTablet) { - int iconRes = LegacyUserUiHelper.getUserSwitcherActionIconResourceId( - item.isAddUser, item.isGuest, item.isAddSupervisedUser, isTablet); - return context.getDrawable(iconRes); - } - - public void refresh() { - mController.refreshUsers(UserHandle.USER_NULL); - } - } - private void checkIfAddUserDisallowedByAdminOnly(UserRecord record) { EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext, UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId()); @@ -1178,20 +995,17 @@ public class UserSwitcherController implements Dumpable { defaultSimpleUserSwitcher, UserHandle.USER_SYSTEM) != 0; } + @Override public void startActivity(Intent intent) { mActivityStarter.startActivity(intent, true); } - /** - * Add a subscriber to when user switches. - */ + @Override public void addUserSwitchCallback(UserSwitchCallback callback) { mUserSwitchCallbacks.add(callback); } - /** - * Remove a subscriber to when user switches. - */ + @Override public void removeUserSwitchCallback(UserSwitchCallback callback) { mUserSwitchCallbacks.remove(callback); } @@ -1218,7 +1032,7 @@ public class UserSwitcherController implements Dumpable { // which // helps making the transition faster. if (!mKeyguardStateController.isShowing()) { - mHandler.post(UserSwitcherController.this::notifyAdapters); + mHandler.post(UserSwitcherControllerOldImpl.this::notifyAdapters); } else { notifyAdapters(); } @@ -1367,13 +1181,4 @@ public class UserSwitcherController implements Dumpable { } } - /** - * Callback to for when this controller receives the intent to switch users. - */ - public interface UserSwitchCallback { - /** - * Called when user has switched. - */ - void onUserSwitched(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 1b7353923ada..b1b45b51d8e4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -58,6 +58,8 @@ import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.statusbar.policy.SecurityControllerImpl; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; +import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.statusbar.policy.UserSwitcherControllerImpl; import com.android.systemui.statusbar.policy.WalletController; import com.android.systemui.statusbar.policy.WalletControllerImpl; import com.android.systemui.statusbar.policy.ZenModeController; @@ -196,4 +198,8 @@ public interface StatusBarPolicyModule { static DataSaverController provideDataSaverController(NetworkController networkController) { return networkController.getDataSaverController(); } + + /** Binds {@link UserSwitcherController} to its implementation. */ + @Binds + UserSwitcherController bindUserSwitcherController(UserSwitcherControllerImpl impl); } diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt index 5e2dde6be046..108ab43977e9 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt @@ -53,10 +53,8 @@ import com.android.systemui.flags.Flags import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager.LOW_PENALTY import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter import com.android.systemui.statusbar.policy.UserSwitcherController -import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter -import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA -import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA import com.android.systemui.user.data.source.UserRecord import com.android.systemui.user.ui.binder.UserSwitcherViewBinder import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel @@ -66,10 +64,10 @@ import kotlin.math.ceil private const val USER_VIEW = "user_view" -/** - * Support a fullscreen user switcher - */ -open class UserSwitcherActivity @Inject constructor( +/** Support a fullscreen user switcher */ +open class UserSwitcherActivity +@Inject +constructor( private val userSwitcherController: UserSwitcherController, private val broadcastDispatcher: BroadcastDispatcher, private val falsingCollector: FalsingCollector, @@ -86,11 +84,12 @@ open class UserSwitcherActivity @Inject constructor( private lateinit var addButton: View private var addUserRecords = mutableListOf<UserRecord>() private val onBackCallback = OnBackInvokedCallback { finish() } - private val userSwitchedCallback: UserTracker.Callback = object : UserTracker.Callback { - override fun onUserChanged(newUser: Int, userContext: Context) { - finish() + private val userSwitchedCallback: UserTracker.Callback = + object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + finish() + } } - } // When the add users options become available, insert another option to manage users private val manageUserRecord = UserRecord( @@ -114,13 +113,14 @@ open class UserSwitcherActivity @Inject constructor( @VisibleForTesting fun createActivity() { setContentView(R.layout.user_switcher_fullscreen) - window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE - or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) + window.decorView.systemUiVisibility = + (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) if (isUsingModernArchitecture()) { Log.d(TAG, "Using modern architecture.") - val viewModel = ViewModelProvider( - this, viewModelFactory.get())[UserSwitcherViewModel::class.java] + val viewModel = + ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java] UserSwitcherViewBinder.bind( view = requireViewById(R.id.user_switcher_root), viewModel = viewModel, @@ -136,27 +136,23 @@ open class UserSwitcherActivity @Inject constructor( parent = requireViewById<UserSwitcherRootView>(R.id.user_switcher_root) - parent.touchHandler = object : Gefingerpoken { - override fun onTouchEvent(ev: MotionEvent?): Boolean { - falsingCollector.onTouchEvent(ev) - return false + parent.touchHandler = + object : Gefingerpoken { + override fun onTouchEvent(ev: MotionEvent?): Boolean { + falsingCollector.onTouchEvent(ev) + return false + } } - } - requireViewById<View>(R.id.cancel).apply { - setOnClickListener { - _ -> finish() - } - } + requireViewById<View>(R.id.cancel).apply { setOnClickListener { _ -> finish() } } - addButton = requireViewById<View>(R.id.add).apply { - setOnClickListener { - _ -> showPopupMenu() - } - } + addButton = + requireViewById<View>(R.id.add).apply { setOnClickListener { _ -> showPopupMenu() } } onBackInvokedDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackCallback) + OnBackInvokedDispatcher.PRIORITY_DEFAULT, + onBackCallback + ) userSwitcherController.init(parent) initBroadcastReceiver() @@ -169,25 +165,30 @@ open class UserSwitcherActivity @Inject constructor( val items = mutableListOf<UserRecord>() addUserRecords.forEach { items.add(it) } - var popupMenuAdapter = ItemAdapter( - this, - R.layout.user_switcher_fullscreen_popup_item, - layoutInflater, - { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) }, - { item: UserRecord -> adapter.findUserIcon(item, true).mutate().apply { - setTint(resources.getColor( - R.color.user_switcher_fullscreen_popup_item_tint, - getTheme() - )) - } } - ) + var popupMenuAdapter = + ItemAdapter( + this, + R.layout.user_switcher_fullscreen_popup_item, + layoutInflater, + { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) }, + { item: UserRecord -> + adapter.findUserIcon(item, true).mutate().apply { + setTint( + resources.getColor( + R.color.user_switcher_fullscreen_popup_item_tint, + getTheme() + ) + ) + } + } + ) popupMenuAdapter.addAll(items) - popupMenu = UserSwitcherPopupMenu(this).apply { - setAnchorView(addButton) - setAdapter(popupMenuAdapter) - setOnItemClickListener { - parent: AdapterView<*>, view: View, pos: Int, id: Long -> + popupMenu = + UserSwitcherPopupMenu(this).apply { + setAnchorView(addButton) + setAdapter(popupMenuAdapter) + setOnItemClickListener { parent: AdapterView<*>, view: View, pos: Int, id: Long -> if (falsingManager.isFalseTap(LOW_PENALTY) || !view.isEnabled()) { return@setOnItemClickListener } @@ -206,10 +207,10 @@ open class UserSwitcherActivity @Inject constructor( if (!item.isAddUser) { this@UserSwitcherActivity.finish() } - } + } - show() - } + show() + } } private fun buildUserViews() { @@ -227,8 +228,8 @@ open class UserSwitcherActivity @Inject constructor( val totalWidth = parent.width val userViewCount = adapter.getTotalUserViews() val maxColumns = getMaxColumns(userViewCount) - val horizontalGap = resources - .getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap) + val horizontalGap = + resources.getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap) val totalWidthOfHorizontalGap = (maxColumns - 1) * horizontalGap val maxWidgetDiameter = (totalWidth - totalWidthOfHorizontalGap) / maxColumns @@ -299,14 +300,15 @@ open class UserSwitcherActivity @Inject constructor( } private fun initBroadcastReceiver() { - broadcastReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val action = intent.getAction() - if (Intent.ACTION_SCREEN_OFF.equals(action)) { - finish() + broadcastReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val action = intent.getAction() + if (Intent.ACTION_SCREEN_OFF.equals(action)) { + finish() + } } } - } val filter = IntentFilter() filter.addAction(Intent.ACTION_SCREEN_OFF) @@ -322,9 +324,7 @@ open class UserSwitcherActivity @Inject constructor( return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY) } - /** - * Provides views to populate the option menu. - */ + /** Provides views to populate the option menu. */ private class ItemAdapter( val parentContext: Context, val resource: Int, @@ -337,43 +337,27 @@ open class UserSwitcherActivity @Inject constructor( val item = getItem(position) val view = convertView ?: layoutInflater.inflate(resource, parent, false) - view.requireViewById<ImageView>(R.id.icon).apply { - setImageDrawable(iconGetter(item)) - } - view.requireViewById<TextView>(R.id.text).apply { - setText(textGetter(item)) - } + view.requireViewById<ImageView>(R.id.icon).apply { setImageDrawable(iconGetter(item)) } + view.requireViewById<TextView>(R.id.text).apply { setText(textGetter(item)) } return view } } - private inner class UserAdapter : BaseUserAdapter(userSwitcherController) { + private inner class UserAdapter : BaseUserSwitcherAdapter(userSwitcherController) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val item = getItem(position) var view = convertView as ViewGroup? if (view == null) { - view = layoutInflater.inflate( - R.layout.user_switcher_fullscreen_item, - parent, - false - ) as ViewGroup - } - (view.getChildAt(0) as ImageView).apply { - setImageDrawable(getDrawable(item)) - } - (view.getChildAt(1) as TextView).apply { - setText(getName(getContext(), item)) + view = + layoutInflater.inflate(R.layout.user_switcher_fullscreen_item, parent, false) + as ViewGroup } + (view.getChildAt(0) as ImageView).apply { setImageDrawable(getDrawable(item)) } + (view.getChildAt(1) as TextView).apply { setText(getName(getContext(), item)) } view.setEnabled(item.isSwitchToEnabled) - view.setAlpha( - if (view.isEnabled()) { - USER_SWITCH_ENABLED_ALPHA - } else { - USER_SWITCH_DISABLED_ALPHA - } - ) + UserSwitcherController.setSelectableAlpha(view) view.setTag(USER_VIEW) return view } @@ -401,23 +385,20 @@ open class UserSwitcherActivity @Inject constructor( } fun getTotalUserViews(): Int { - return users.count { item -> - !doNotRenderUserView(item) - } + return users.count { item -> !doNotRenderUserView(item) } } fun doNotRenderUserView(item: UserRecord): Boolean { - return item.isAddUser || - item.isAddSupervisedUser || - item.isGuest && item.info == null + return item.isAddUser || item.isAddSupervisedUser || item.isGuest && item.info == null } private fun getDrawable(item: UserRecord): Drawable { - var drawable = if (item.isGuest) { - getDrawable(R.drawable.ic_account_circle) - } else { - findUserIcon(item) - } + var drawable = + if (item.isGuest) { + getDrawable(R.drawable.ic_account_circle) + } else { + findUserIcon(item) + } drawable.mutate() if (!item.isCurrent && !item.isSwitchToEnabled) { @@ -429,16 +410,16 @@ open class UserSwitcherActivity @Inject constructor( ) } - val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate() - as LayerDrawable - if (item == userSwitcherController.getCurrentUserRecord()) { + val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate() as LayerDrawable + if (item == userSwitcherController.currentUserRecord) { (ld.findDrawableByLayerId(R.id.ring) as GradientDrawable).apply { - val stroke = resources - .getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width) - val color = Utils.getColorAttrDefaultColor( - this@UserSwitcherActivity, - com.android.internal.R.attr.colorAccentPrimary - ) + val stroke = + resources.getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width) + val color = + Utils.getColorAttrDefaultColor( + this@UserSwitcherActivity, + com.android.internal.R.attr.colorAccentPrimary + ) setStroke(stroke, color) } diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 305b5ee920a1..035638800f9c 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -99,7 +99,7 @@ constructor( override val actions: Flow<List<UserActionModel>> = userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } } - override val isActionableWhenLocked: Flow<Boolean> = controller.addUsersFromLockScreen + override val isActionableWhenLocked: Flow<Boolean> = controller.isAddUsersFromLockScreenEnabled override val isGuestUserAutoCreated: Boolean = controller.isGuestUserAutoCreated diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt index 485a7e5738f1..aca60c033bac 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt @@ -86,13 +86,12 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel( becauseCannotSkipBouncer = false, biometricSettingEnabledForUser = false, bouncerFullyShown = false, - onlyFaceEnrolled = false, faceAuthenticated = false, faceDisabled = false, faceLockedOut = false, fpLockedOut = false, goingToSleep = false, - keyguardAwakeExcludingBouncerShowing = false, + keyguardAwake = false, keyguardGoingAway = false, listeningForFaceAssistant = false, occludingAppRequestingFaceAuth = false, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 28e99da49496..43f6f1aac097 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -116,9 +116,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User"); - when(mUserSwitcherController.getKeyguardStateController()) - .thenReturn(mKeyguardStateController); - when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mUserSwitcherController.isKeyguardShowing()).thenReturn(true); mScreenWidth = getUiDevice().getDisplayWidth(); mFakeMeasureSpec = View diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 409457e23ce9..9c64c1b06a17 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -212,8 +212,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mBiometricEnabledCallbackArgCaptor; @Captor private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor; - @Captor - private ArgumentCaptor<CancellationSignal> mCancellationSignalCaptor; // Direct executor private final Executor mBackgroundExecutor = Runnable::run; @@ -596,13 +594,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testTriesToAuthenticate_whenBouncer() { - fingerprintIsNotEnrolled(); - faceAuthEnabled(); setKeyguardBouncerVisibility(true); verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); - verify(mFaceManager, atLeastOnce()).isHardwareDetected(); - verify(mFaceManager, atLeastOnce()).hasEnrolledTemplates(anyInt()); + verify(mFaceManager).isHardwareDetected(); + verify(mFaceManager).hasEnrolledTemplates(anyInt()); } @Test @@ -1237,9 +1233,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { public void testShouldListenForFace_whenFaceIsAlreadyAuthenticated_returnsFalse() throws RemoteException { // Face auth should run when the following is true. - faceAuthEnabled(); bouncerFullyVisibleAndNotGoingToSleep(); - fingerprintIsNotEnrolled(); keyguardNotGoingAway(); currentUserIsPrimary(); strongAuthNotRequired(); @@ -1266,7 +1260,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext); - // Preconditions for face auth to run + // Face auth should run when the following is true. keyguardNotGoingAway(); bouncerFullyVisibleAndNotGoingToSleep(); strongAuthNotRequired(); @@ -1283,7 +1277,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenStrongAuthDoesNotAllowScanning_returnsFalse() throws RemoteException { - // Preconditions for face auth to run + // Face auth should run when the following is true. keyguardNotGoingAway(); bouncerFullyVisibleAndNotGoingToSleep(); currentUserIsPrimary(); @@ -1304,11 +1298,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenBiometricsDisabledForUser_returnsFalse() throws RemoteException { - // Preconditions for face auth to run - faceAuthEnabled(); keyguardNotGoingAway(); bouncerFullyVisibleAndNotGoingToSleep(); - fingerprintIsNotEnrolled(); currentUserIsPrimary(); currentUserDoesNotHaveTrust(); biometricsNotDisabledThroughDevicePolicyManager(); @@ -1328,11 +1319,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenUserCurrentlySwitching_returnsFalse() throws RemoteException { - // Preconditions for face auth to run - faceAuthEnabled(); + // Face auth should run when the following is true. keyguardNotGoingAway(); bouncerFullyVisibleAndNotGoingToSleep(); - fingerprintIsNotEnrolled(); currentUserIsPrimary(); currentUserDoesNotHaveTrust(); biometricsNotDisabledThroughDevicePolicyManager(); @@ -1351,11 +1340,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenSecureCameraLaunched_returnsFalse() throws RemoteException { - // Preconditions for face auth to run - faceAuthEnabled(); keyguardNotGoingAway(); bouncerFullyVisibleAndNotGoingToSleep(); - fingerprintIsNotEnrolled(); currentUserIsPrimary(); currentUserDoesNotHaveTrust(); biometricsNotDisabledThroughDevicePolicyManager(); @@ -1374,7 +1360,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenOccludingAppRequestsFaceAuth_returnsTrue() throws RemoteException { - // Preconditions for face auth to run + // Face auth should run when the following is true. keyguardNotGoingAway(); bouncerFullyVisibleAndNotGoingToSleep(); currentUserIsPrimary(); @@ -1397,8 +1383,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenBouncerShowingAndDeviceIsAwake_returnsTrue() throws RemoteException { - // Preconditions for face auth to run - faceAuthEnabled(); + // Face auth should run when the following is true. keyguardNotGoingAway(); currentUserIsPrimary(); currentUserDoesNotHaveTrust(); @@ -1410,7 +1395,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); bouncerFullyVisibleAndNotGoingToSleep(); - fingerprintIsNotEnrolled(); mTestableLooper.processAllMessages(); assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); @@ -1419,7 +1403,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenAuthInterruptIsActive_returnsTrue() throws RemoteException { - // Preconditions for face auth to run + // Face auth should run when the following is true. keyguardNotGoingAway(); currentUserIsPrimary(); currentUserDoesNotHaveTrust(); @@ -1445,7 +1429,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { biometricsNotDisabledThroughDevicePolicyManager(); biometricsEnabledForCurrentUser(); userNotCurrentlySwitching(); - bouncerFullyVisible(); statusBarShadeIsLocked(); mTestableLooper.processAllMessages(); @@ -1459,9 +1442,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { keyguardIsVisible(); assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); statusBarShadeIsNotLocked(); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - bouncerNotFullyVisible(); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); } @@ -1523,44 +1503,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testBouncerVisibility_whenBothFingerprintAndFaceIsEnrolled_stopsFaceAuth() - throws RemoteException { - // Both fingerprint and face are enrolled by default - // Preconditions for face auth to run - keyguardNotGoingAway(); - currentUserIsPrimary(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - deviceNotGoingToSleep(); - deviceIsInteractive(); - statusBarShadeIsNotLocked(); - keyguardIsVisible(); - - mTestableLooper.processAllMessages(); - clearInvocations(mUiEventLogger); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - - mKeyguardUpdateMonitor.requestFaceAuth(true, - FaceAuthApiRequestReason.UDFPS_POINTER_DOWN); - - verify(mFaceManager).authenticate(any(), - mCancellationSignalCaptor.capture(), - mAuthenticationCallbackCaptor.capture(), - any(), - anyInt(), - anyBoolean()); - CancellationSignal cancelSignal = mCancellationSignalCaptor.getValue(); - - bouncerFullyVisible(); - mTestableLooper.processAllMessages(); - - assertThat(cancelSignal.isCanceled()).isTrue(); - } - - @Test public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() { mKeyguardUpdateMonitor.dispatchStartedWakingUp(); mTestableLooper.processAllMessages(); @@ -1623,21 +1565,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, ""); } - private void faceAuthEnabled() { - // this ensures KeyguardUpdateMonitor updates the cached mIsFaceEnrolled flag using the - // face manager mock wire-up in setup() - mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(mCurrentUserId); - } - - private void fingerprintIsNotEnrolled() { - when(mFingerprintManager.hasEnrolledTemplates(mCurrentUserId)).thenReturn(false); - // This updates the cached fingerprint state. - // There is no straightforward API to update the fingerprint state. - // It currently works updates after enrollment changes because something else invokes - // startListeningForFingerprint(), which internally calls this method. - mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(mCurrentUserId); - } - private void statusBarShadeIsNotLocked() { mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD); } @@ -1744,10 +1671,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.dispatchStartedWakingUp(); } - private void bouncerNotFullyVisible() { - setKeyguardBouncerVisibility(false); - } - private void bouncerFullyVisible() { setKeyguardBouncerVisibility(true); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index d70467ddeebe..c5a7de410eb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -36,6 +36,7 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.complication.ComplicationHostViewController; +import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor; import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback; @@ -88,6 +89,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { @Mock ViewRootImpl mViewRoot; + @Mock + BouncerCallbackInteractor mBouncerCallbackInteractor; + DreamOverlayContainerViewController mController; @Before @@ -110,7 +114,8 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mResources, MAX_BURN_IN_OFFSET, BURN_IN_PROTECTION_UPDATE_INTERVAL, - MILLIS_UNTIL_FULL_JITTER); + MILLIS_UNTIL_FULL_JITTER, + mBouncerCallbackInteractor); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java index bc944404efe6..522b5b5a8530 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java @@ -16,17 +16,28 @@ package com.android.systemui.dreams.complication; +import static com.android.systemui.flags.Flags.DREAM_MEDIA_TAP_TO_OPEN; + import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.app.PendingIntent; +import android.content.Intent; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; import androidx.test.filters.SmallTest; +import com.android.systemui.ActivityIntentHelper; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.media.MediaCarouselController; import com.android.systemui.media.dream.MediaDreamComplication; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; import org.junit.Test; @@ -48,21 +59,52 @@ public class DreamMediaEntryComplicationTest extends SysuiTestCase { @Mock private MediaDreamComplication mMediaComplication; + @Mock + private MediaCarouselController mMediaCarouselController; + + @Mock + private ActivityStarter mActivityStarter; + + @Mock + private ActivityIntentHelper mActivityIntentHelper; + + @Mock + private KeyguardStateController mKeyguardStateController; + + @Mock + private NotificationLockscreenUserManager mLockscreenUserManager; + + @Mock + private FeatureFlags mFeatureFlags; + + @Mock + private PendingIntent mPendingIntent; + + private final Intent mIntent = new Intent("android.test.TEST_ACTION"); + private final Integer mCurrentUserId = 99; + @Before public void setup() { MockitoAnnotations.initMocks(this); + when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(false); } /** * Ensures clicking media entry chip adds/removes media complication. */ @Test - public void testClick() { + public void testClickToOpenUMO() { final DreamMediaEntryComplication.DreamMediaEntryViewController viewController = new DreamMediaEntryComplication.DreamMediaEntryViewController( mView, mDreamOverlayStateController, - mMediaComplication); + mMediaComplication, + mMediaCarouselController, + mActivityStarter, + mActivityIntentHelper, + mKeyguardStateController, + mLockscreenUserManager, + mFeatureFlags); final ArgumentCaptor<View.OnClickListener> clickListenerCaptor = ArgumentCaptor.forClass(View.OnClickListener.class); @@ -85,10 +127,90 @@ public class DreamMediaEntryComplicationTest extends SysuiTestCase { new DreamMediaEntryComplication.DreamMediaEntryViewController( mView, mDreamOverlayStateController, - mMediaComplication); + mMediaComplication, + mMediaCarouselController, + mActivityStarter, + mActivityIntentHelper, + mKeyguardStateController, + mLockscreenUserManager, + mFeatureFlags); viewController.onViewDetached(); verify(mView).setSelected(false); verify(mDreamOverlayStateController).removeComplication(mMediaComplication); } + + /** + * Ensures clicking media entry chip opens media when flag is set. + */ + @Test + public void testClickToOpenMediaOverLockscreen() { + when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(true); + + when(mMediaCarouselController.getCurrentVisibleMediaContentIntent()).thenReturn( + mPendingIntent); + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mPendingIntent.getIntent()).thenReturn(mIntent); + when(mLockscreenUserManager.getCurrentUserId()).thenReturn(mCurrentUserId); + + final DreamMediaEntryComplication.DreamMediaEntryViewController viewController = + new DreamMediaEntryComplication.DreamMediaEntryViewController( + mView, + mDreamOverlayStateController, + mMediaComplication, + mMediaCarouselController, + mActivityStarter, + mActivityIntentHelper, + mKeyguardStateController, + mLockscreenUserManager, + mFeatureFlags); + viewController.onViewAttached(); + + final ArgumentCaptor<View.OnClickListener> clickListenerCaptor = + ArgumentCaptor.forClass(View.OnClickListener.class); + verify(mView).setOnClickListener(clickListenerCaptor.capture()); + + when(mActivityIntentHelper.wouldShowOverLockscreen(mIntent, mCurrentUserId)).thenReturn( + true); + + clickListenerCaptor.getValue().onClick(mView); + verify(mActivityStarter).startActivity(mIntent, true, null, true); + } + + /** + * Ensures clicking media entry chip opens media when flag is set. + */ + @Test + public void testClickToOpenMediaDismissingLockscreen() { + when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(true); + + when(mMediaCarouselController.getCurrentVisibleMediaContentIntent()).thenReturn( + mPendingIntent); + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mPendingIntent.getIntent()).thenReturn(mIntent); + when(mLockscreenUserManager.getCurrentUserId()).thenReturn(mCurrentUserId); + + final DreamMediaEntryComplication.DreamMediaEntryViewController viewController = + new DreamMediaEntryComplication.DreamMediaEntryViewController( + mView, + mDreamOverlayStateController, + mMediaComplication, + mMediaCarouselController, + mActivityStarter, + mActivityIntentHelper, + mKeyguardStateController, + mLockscreenUserManager, + mFeatureFlags); + viewController.onViewAttached(); + + final ArgumentCaptor<View.OnClickListener> clickListenerCaptor = + ArgumentCaptor.forClass(View.OnClickListener.class); + verify(mView).setOnClickListener(clickListenerCaptor.capture()); + + when(mActivityIntentHelper.wouldShowOverLockscreen(mIntent, mCurrentUserId)).thenReturn( + false); + + clickListenerCaptor.getValue().onClick(mView); + verify(mActivityStarter).postStartActivityDismissingKeyguard(mPendingIntent, null); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt new file mode 100644 index 000000000000..3a61c57d086f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2022 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.keyguard.domain.interactor + +import android.view.View +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.phone.KeyguardBouncer +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class BouncerCallbackInteractorTest : SysuiTestCase() { + private val bouncerCallbackInteractor = BouncerCallbackInteractor() + @Mock private lateinit var bouncerExpansionCallback: KeyguardBouncer.BouncerExpansionCallback + @Mock private lateinit var keyguardResetCallback: KeyguardBouncer.KeyguardResetCallback + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + bouncerCallbackInteractor.addBouncerExpansionCallback(bouncerExpansionCallback) + bouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback) + } + + @Test + fun testOnFullyShown() { + bouncerCallbackInteractor.dispatchFullyShown() + verify(bouncerExpansionCallback).onFullyShown() + } + + @Test + fun testOnFullyHidden() { + bouncerCallbackInteractor.dispatchFullyHidden() + verify(bouncerExpansionCallback).onFullyHidden() + } + + @Test + fun testOnExpansionChanged() { + bouncerCallbackInteractor.dispatchExpansionChanged(5f) + verify(bouncerExpansionCallback).onExpansionChanged(5f) + } + + @Test + fun testOnVisibilityChanged() { + bouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE) + verify(bouncerExpansionCallback).onVisibilityChanged(false) + } + + @Test + fun testOnStartingToHide() { + bouncerCallbackInteractor.dispatchStartingToHide() + verify(bouncerExpansionCallback).onStartingToHide() + } + + @Test + fun testOnStartingToShow() { + bouncerCallbackInteractor.dispatchStartingToShow() + verify(bouncerExpansionCallback).onStartingToShow() + } + + @Test + fun testOnKeyguardReset() { + bouncerCallbackInteractor.dispatchReset() + verify(keyguardResetCallback).onKeyguardReset() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt new file mode 100644 index 000000000000..e6c8dd87d982 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2022 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.keyguard.domain.interactor + +import android.os.Looper +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.DejankUtils +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.keyguard.data.BouncerView +import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository +import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel +import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel +import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN +import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.mockito.any +import com.android.systemui.utils.os.FakeHandler +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Answers +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWithLooper(setAsMainLooper = true) +@RunWith(AndroidTestingRunner::class) +class BouncerInteractorTest : SysuiTestCase() { + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private lateinit var repository: KeyguardBouncerRepository + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel + @Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor + @Mock private lateinit var falsingCollector: FalsingCollector + @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry + @Mock private lateinit var keyguardBypassController: KeyguardBypassController + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + private val mainHandler = FakeHandler(Looper.getMainLooper()) + private lateinit var bouncerInteractor: BouncerInteractor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + DejankUtils.setImmediate(true) + bouncerInteractor = + BouncerInteractor( + repository, + bouncerView, + mainHandler, + keyguardStateController, + keyguardSecurityModel, + bouncerCallbackInteractor, + falsingCollector, + dismissCallbackRegistry, + keyguardBypassController, + keyguardUpdateMonitor, + ) + `when`(repository.startingDisappearAnimation.value).thenReturn(null) + `when`(repository.show.value).thenReturn(null) + } + + @Test + fun testShow_isScrimmed() { + bouncerInteractor.show(true) + verify(repository).setShowMessage(null) + verify(repository).setOnScreenTurnedOff(false) + verify(repository).setKeyguardAuthenticated(null) + verify(repository).setHide(false) + verify(repository).setStartingToHide(false) + verify(repository).setScrimmed(true) + verify(repository).setExpansion(EXPANSION_VISIBLE) + verify(repository).setShowingSoon(true) + verify(keyguardStateController).notifyBouncerShowing(true) + verify(bouncerCallbackInteractor).dispatchStartingToShow() + verify(repository).setVisible(true) + verify(repository).setShow(any(KeyguardBouncerModel::class.java)) + verify(repository).setShowingSoon(false) + } + + @Test + fun testShow_isNotScrimmed() { + verify(repository, never()).setExpansion(EXPANSION_VISIBLE) + } + + @Test + fun testShow_keyguardIsDone() { + `when`(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true) + verify(keyguardStateController, never()).notifyBouncerShowing(true) + verify(bouncerCallbackInteractor, never()).dispatchStartingToShow() + } + + @Test + fun testHide() { + bouncerInteractor.hide() + verify(falsingCollector).onBouncerHidden() + verify(keyguardStateController).notifyBouncerShowing(false) + verify(repository).setShowingSoon(false) + verify(repository).setOnDismissAction(null) + verify(repository).setVisible(false) + verify(repository).setHide(true) + verify(repository).setShow(null) + } + + @Test + fun testExpansion() { + `when`(repository.expansionAmount.value).thenReturn(0.5f) + bouncerInteractor.setExpansion(0.6f) + verify(repository).setExpansion(0.6f) + verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f) + } + + @Test + fun testExpansion_fullyShown() { + `when`(repository.expansionAmount.value).thenReturn(0.5f) + `when`(repository.startingDisappearAnimation.value).thenReturn(null) + bouncerInteractor.setExpansion(EXPANSION_VISIBLE) + verify(falsingCollector).onBouncerShown() + verify(bouncerCallbackInteractor).dispatchFullyShown() + } + + @Test + fun testExpansion_fullyHidden() { + `when`(repository.expansionAmount.value).thenReturn(0.5f) + `when`(repository.startingDisappearAnimation.value).thenReturn(null) + bouncerInteractor.setExpansion(EXPANSION_HIDDEN) + verify(repository).setVisible(false) + verify(repository).setShow(null) + verify(falsingCollector).onBouncerHidden() + verify(bouncerCallbackInteractor).dispatchReset() + verify(bouncerCallbackInteractor).dispatchFullyHidden() + } + + @Test + fun testExpansion_startingToHide() { + `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE) + bouncerInteractor.setExpansion(0.1f) + verify(repository).setStartingToHide(true) + verify(bouncerCallbackInteractor).dispatchStartingToHide() + } + + @Test + fun testShowMessage() { + bouncerInteractor.showMessage("abc", null) + verify(repository).setShowMessage(BouncerShowMessageModel("abc", null)) + } + + @Test + fun testDismissAction() { + val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java) + val cancelAction = mock(Runnable::class.java) + bouncerInteractor.setDismissAction(onDismissAction, cancelAction) + verify(repository) + .setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction)) + } + + @Test + fun testUpdateResources() { + bouncerInteractor.updateResources() + verify(repository).setResourceUpdateRequests(true) + } + + @Test + fun testNotifyKeyguardAuthenticated() { + bouncerInteractor.notifyKeyguardAuthenticated(true) + verify(repository).setKeyguardAuthenticated(true) + } + + @Test + fun testOnScreenTurnedOff() { + bouncerInteractor.onScreenTurnedOff() + verify(repository).setOnScreenTurnedOff(true) + } + + @Test + fun testSetKeyguardPosition() { + bouncerInteractor.setKeyguardPosition(0f) + verify(repository).setKeyguardPosition(0f) + } + + @Test + fun testNotifyKeyguardAuthenticatedHandled() { + bouncerInteractor.notifyKeyguardAuthenticatedHandled() + verify(repository).setKeyguardAuthenticated(null) + } + + @Test + fun testNotifyUpdatedResources() { + bouncerInteractor.notifyUpdatedResources() + verify(repository).setResourceUpdateRequests(false) + } + + @Test + fun testSetBackButtonEnabled() { + bouncerInteractor.setBackButtonEnabled(true) + verify(repository).setIsBackButtonEnabled(true) + } + + @Test + fun testStartDisappearAnimation() { + val runnable = mock(Runnable::class.java) + bouncerInteractor.startDisappearAnimation(runnable) + verify(repository).setStartDisappearAnimation(any(Runnable::class.java)) + } + + @Test + fun testIsFullShowing() { + `when`(repository.isVisible.value).thenReturn(true) + `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE) + `when`(repository.startingDisappearAnimation.value).thenReturn(null) + assertThat(bouncerInteractor.isFullyShowing()).isTrue() + `when`(repository.isVisible.value).thenReturn(false) + assertThat(bouncerInteractor.isFullyShowing()).isFalse() + } + + @Test + fun testIsScrimmed() { + `when`(repository.isScrimmed.value).thenReturn(true) + assertThat(bouncerInteractor.isScrimmed()).isTrue() + `when`(repository.isScrimmed.value).thenReturn(false) + assertThat(bouncerInteractor.isScrimmed()).isFalse() + } + + @Test + fun testIsInTransit() { + `when`(repository.showingSoon.value).thenReturn(true) + assertThat(bouncerInteractor.isInTransit()).isTrue() + `when`(repository.showingSoon.value).thenReturn(false) + assertThat(bouncerInteractor.isInTransit()).isFalse() + `when`(repository.expansionAmount.value).thenReturn(0.5f) + assertThat(bouncerInteractor.isInTransit()).isTrue() + } + + @Test + fun testIsAnimatingAway() { + `when`(repository.startingDisappearAnimation.value).thenReturn(Runnable {}) + assertThat(bouncerInteractor.isAnimatingAway()).isTrue() + `when`(repository.startingDisappearAnimation.value).thenReturn(null) + assertThat(bouncerInteractor.isAnimatingAway()).isFalse() + } + + @Test + fun testWillDismissWithAction() { + `when`(repository.onDismissAction.value?.onDismissAction) + .thenReturn(mock(ActivityStarter.OnDismissAction::class.java)) + assertThat(bouncerInteractor.willDismissWithAction()).isTrue() + `when`(repository.onDismissAction.value?.onDismissAction).thenReturn(null) + assertThat(bouncerInteractor.willDismissWithAction()).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt index 5dd1cfcb76e4..e3e3b7413157 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.media +import android.app.PendingIntent import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest @@ -43,6 +44,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever @@ -366,7 +368,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { playerIndex, mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex ) - assertEquals( playerIndex, 0) + assertEquals(playerIndex, 0) // Replaying the same media player one more time. // And check that the card stays in its position. @@ -402,4 +404,44 @@ class MediaCarouselControllerTest : SysuiTestCase() { visualStabilityCallback.value.onReorderingAllowed() assertEquals(true, result) } + + @Test + fun testGetCurrentVisibleMediaContentIntent() { + val clickIntent1 = mock(PendingIntent::class.java) + val player1 = Triple("player1", + DATA.copy(clickIntent = clickIntent1), + 1000L) + clock.setCurrentTimeMillis(player1.third) + MediaPlayerData.addMediaPlayer(player1.first, + player1.second.copy(notificationKey = player1.first), + panel, clock, isSsReactivated = false) + + assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1) + + val clickIntent2 = mock(PendingIntent::class.java) + val player2 = Triple("player2", + DATA.copy(clickIntent = clickIntent2), + 2000L) + clock.setCurrentTimeMillis(player2.third) + MediaPlayerData.addMediaPlayer(player2.first, + player2.second.copy(notificationKey = player2.first), + panel, clock, isSsReactivated = false) + + // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is + // added to the front because it was active more recently. + assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2) + + val clickIntent3 = mock(PendingIntent::class.java) + val player3 = Triple("player3", + DATA.copy(clickIntent = clickIntent3), + 500L) + clock.setCurrentTimeMillis(player3.third) + MediaPlayerData.addMediaPlayer(player3.first, + player3.second.copy(notificationKey = player3.first), + panel, clock, isSsReactivated = false) + + // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is + // added to the end because it was active less recently. + assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java index 0bfc0344b521..2f52950a9ee4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java @@ -16,7 +16,7 @@ package com.android.systemui.media.dream; -import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION; +import static com.android.systemui.flags.Flags.DREAM_MEDIA_COMPLICATION; import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; @@ -68,7 +68,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); - when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(true); + when(mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)).thenReturn(true); } @Test @@ -137,7 +137,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { @Test public void testOnMediaDataLoaded_mediaComplicationDisabled_doesNotAddComplication() { - when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(false); + when(mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)).thenReturn(false); final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 2adc389a964e..481e4e9992d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -21,12 +21,16 @@ import android.testing.TestableLooper.RunWithLooper import android.view.MotionEvent import android.view.ViewGroup import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardHostViewController import com.android.keyguard.LockIconViewController +import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.dock.DockManager +import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.KeyguardUnlockAnimationController +import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.NotificationShadeDepthController @@ -51,9 +55,9 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +@SmallTest @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) -@SmallTest class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var view: NotificationShadeWindowView @@ -72,8 +76,12 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController @Mock + private lateinit var featureFlags: FeatureFlags + @Mock private lateinit var ambientState: AmbientState @Mock + private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel + @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager @@ -87,6 +95,10 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController @Mock private lateinit var pulsingGestureListener: PulsingGestureListener + @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory + @Mock lateinit var keyguardBouncerContainer: ViewGroup + @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent + @Mock lateinit var keyguardHostViewController: KeyguardHostViewController private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler> private lateinit var interactionEventHandler: InteractionEventHandler @@ -97,7 +109,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) whenever(view.bottom).thenReturn(VIEW_BOTTOM) - underTest = NotificationShadeWindowViewController( lockscreenShadeTransitionController, FalsingCollectorFake(), @@ -115,7 +126,10 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { notificationShadeWindowController, keyguardUnlockAnimationController, ambientState, - pulsingGestureListener + pulsingGestureListener, + featureFlags, + keyguardBouncerViewModel, + keyguardBouncerComponentFactory ) underTest.setupExpandedStatusBar() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java index 001bfeeabe6f..4a7dec912895 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java @@ -33,11 +33,14 @@ import android.view.MotionEvent; import androidx.test.filters.SmallTest; import com.android.keyguard.LockIconViewController; +import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.dock.DockManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationShadeDepthController; @@ -86,6 +89,9 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock private AmbientState mAmbientState; @Mock private PulsingGestureListener mPulsingGestureListener; + @Mock private FeatureFlags mFeatureFlags; + @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel; + @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory; @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler> mInteractionEventHandlerCaptor; @@ -121,7 +127,10 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mNotificationShadeWindowController, mKeyguardUnlockAnimationController, mAmbientState, - mPulsingGestureListener + mPulsingGestureListener, + mFeatureFlags, + mKeyguardBouncerViewModel, + mKeyguardBouncerComponentFactory ); mController.setupExpandedStatusBar(); mController.setDragDownHelper(mDragDownHelper); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index a4453f854ce5..ee4b9d9c93f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -41,11 +41,17 @@ import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardMessageArea; import com.android.keyguard.KeyguardMessageAreaController; +import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.keyguard.data.BouncerView; +import com.android.systemui.keyguard.data.BouncerViewDelegate; +import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor; +import com.android.systemui.keyguard.domain.interactor.BouncerInteractor; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.shade.NotificationPanelViewController; @@ -101,6 +107,13 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent; @Mock private DreamOverlayStateController mDreamOverlayStateController; @Mock private LatencyTracker mLatencyTracker; + @Mock private FeatureFlags mFeatureFlags; + @Mock private KeyguardSecurityModel mKeyguardSecurityModel; + @Mock private BouncerCallbackInteractor mBouncerCallbackInteractor; + @Mock private BouncerInteractor mBouncerInteractor; + @Mock private BouncerView mBouncerView; +// @Mock private WeakReference<BouncerViewDelegate> mBouncerViewDelegateWeakReference; + @Mock private BouncerViewDelegate mBouncerViewDelegate; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback; @@ -115,6 +128,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea); when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class))) .thenReturn(mKeyguardMessageAreaController); + when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate); + mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager( getContext(), @@ -133,7 +148,12 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mKeyguardMessageAreaFactory, Optional.of(mSysUiUnfoldComponent), () -> mShadeController, - mLatencyTracker); + mLatencyTracker, + mKeyguardSecurityModel, + mFeatureFlags, + mBouncerCallbackInteractor, + mBouncerInteractor, + mBouncerView); mStatusBarKeyguardViewManager.registerCentralSurfaces( mCentralSurfaces, mNotificationPanelView, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt index 37c0f3621b6f..bf432388ad28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt @@ -34,14 +34,14 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper @SmallTest -class StatusBarUserSwitcherControllerTest : SysuiTestCase() { +class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() { @Mock private lateinit var tracker: StatusBarUserInfoTracker diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt new file mode 100644 index 000000000000..f3046477f4d1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2022 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.policy + +import android.content.pm.UserInfo +import android.graphics.Bitmap +import android.os.UserHandle +import android.view.View +import android.view.ViewGroup +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.user.UserSwitchDialogController +import com.android.systemui.user.data.source.UserRecord +import com.android.systemui.util.mockito.kotlinArgumentCaptor +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import java.lang.ref.WeakReference +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class BaseUserSwitcherAdapterTest : SysuiTestCase() { + + @Mock private lateinit var controller: UserSwitcherController + + private lateinit var underTest: BaseUserSwitcherAdapter + + private lateinit var users: ArrayList<UserRecord> + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + users = + ArrayList( + listOf( + createUserRecord( + id = 0, + picture = mock(), + isSelected = true, + isGuest = false, + ), + createUserRecord( + id = 1, + picture = mock(), + isSelected = false, + isGuest = false, + ), + createUserRecord( + id = UserHandle.USER_NULL, + picture = null, + isSelected = false, + isGuest = true, + ), + ) + ) + + whenever(controller.users).thenAnswer { users } + + underTest = + object : BaseUserSwitcherAdapter(controller) { + override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { + return mock() + } + } + } + + @Test + fun `Adds self to controller in constructor`() { + val captor = kotlinArgumentCaptor<WeakReference<BaseUserSwitcherAdapter>>() + verify(controller).addAdapter(captor.capture()) + + assertThat(captor.value.get()).isEqualTo(underTest) + } + + @Test + fun count() { + assertThat(underTest.count).isEqualTo(users.size) + } + + @Test + fun `count - ignores restricted users when device is locked`() { + whenever(controller.isKeyguardShowing).thenReturn(true) + users = + ArrayList( + listOf( + createUserRecord( + id = 0, + picture = mock(), + isSelected = true, + isGuest = false, + isRestricted = false, + ), + createUserRecord( + id = 1, + picture = mock(), + isSelected = false, + isGuest = false, + isRestricted = true, // this one will be ignored. + ), + createUserRecord( + id = UserHandle.USER_NULL, + picture = null, + isSelected = false, + isGuest = true, + ), + ) + ) + assertThat(underTest.count).isEqualTo(users.size - 1) + } + + @Test + fun `count - does not ignore restricted users when device is not locked`() { + whenever(controller.isKeyguardShowing).thenReturn(false) + users = + ArrayList( + listOf( + createUserRecord( + id = 0, + picture = mock(), + isSelected = true, + isGuest = false, + isRestricted = false, + ), + createUserRecord( + id = 1, + picture = mock(), + isSelected = false, + isGuest = false, + isRestricted = true, + ), + createUserRecord( + id = UserHandle.USER_NULL, + picture = null, + isSelected = false, + isGuest = true, + ), + ) + ) + assertThat(underTest.count).isEqualTo(users.size) + } + + @Test + fun getItem() { + assertThat((0 until underTest.count).map { position -> underTest.getItem(position) }) + .isEqualTo(users) + } + + @Test + fun getItemId() { + (0 until underTest.count).map { position -> + assertThat(underTest.getItemId(position)).isEqualTo(position) + } + } + + @Test + fun onUserListItemClicked() { + val userRecord = users[users.size / 2] + val dialogShower: UserSwitchDialogController.DialogShower = mock() + + underTest.onUserListItemClicked(userRecord, dialogShower) + + verify(controller).onUserListItemClicked(userRecord, dialogShower) + } + + @Test + fun `getName - non guest - returns real name`() { + val userRecord = + createUserRecord( + id = 1, + picture = mock(), + ) + + assertThat(underTest.getName(context, userRecord)).isEqualTo(userRecord.info?.name) + } + + @Test + fun `getName - guest and selected - returns exit guest action name`() { + val expected = "Exit guest" + context.orCreateTestableResources.addOverride( + com.android.settingslib.R.string.guest_exit_quick_settings_button, + expected, + ) + + val userRecord = + createUserRecord( + id = 2, + picture = null, + isGuest = true, + isSelected = true, + ) + + assertThat(underTest.getName(context, userRecord)).isEqualTo(expected) + } + + @Test + fun `getName - guest and not selected - returns enter guest action name`() { + val expected = "Guest" + context.orCreateTestableResources.addOverride( + com.android.internal.R.string.guest_name, + expected, + ) + + val userRecord = + createUserRecord( + id = 2, + picture = null, + isGuest = true, + isSelected = false, + ) + + assertThat(underTest.getName(context, userRecord)).isEqualTo("Guest") + } + + @Test + fun refresh() { + underTest.refresh() + + verify(controller).refreshUsers(UserHandle.USER_NULL) + } + + private fun createUserRecord( + id: Int, + picture: Bitmap? = null, + isSelected: Boolean = false, + isGuest: Boolean = false, + isAction: Boolean = false, + isRestricted: Boolean = false, + ): UserRecord { + return UserRecord( + info = + if (isAction) { + null + } else { + UserInfo(id, "name$id", 0) + }, + picture = picture, + isCurrent = isSelected, + isGuest = isGuest, + isRestricted = isRestricted, + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt index b4f3987b2f95..b86ca6fc5375 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt @@ -38,9 +38,9 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -102,8 +102,7 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() { ViewUtils.attachView(view) testableLooper.processAllMessages() - `when`(userSwitcherController.keyguardStateController).thenReturn(keyguardStateController) - `when`(userSwitcherController.keyguardStateController.isShowing).thenReturn(true) + `when`(userSwitcherController.isKeyguardShowing).thenReturn(true) `when`(keyguardStateController.isShowing).thenReturn(true) `when`(keyguardStateController.isKeyguardGoingAway).thenReturn(false) keyguardQsUserSwitchController.init() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt index 8dcd4bb3b738..76ecc1c7f36d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt @@ -86,7 +86,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest -class UserSwitcherControllerTest : SysuiTestCase() { +class UserSwitcherControllerOldImplTest : SysuiTestCase() { @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var activityManager: IActivityManager @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @@ -118,7 +118,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { private lateinit var longRunningExecutor: FakeExecutor private lateinit var uiExecutor: FakeExecutor private lateinit var uiEventLogger: UiEventLoggerFake - private lateinit var userSwitcherController: UserSwitcherController + private lateinit var userSwitcherController: UserSwitcherControllerOldImpl private lateinit var picture: Bitmap private val ownerId = UserHandle.USER_SYSTEM private val ownerInfo = UserInfo(ownerId, "Owner", null, @@ -205,7 +205,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { } private fun setupController() { - userSwitcherController = UserSwitcherController( + userSwitcherController = + UserSwitcherControllerOldImpl( mContext, activityManager, userManager, @@ -230,7 +231,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { dumpManager, dialogLaunchAnimator, guestResumeSessionReceiver, - guestResetOrExitSessionReceiver) + guestResetOrExitSessionReceiver + ) userSwitcherController.init(notificationShadeWindowView) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index 6b466e1ac2d8..6fec343d036c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -60,7 +60,7 @@ class UserRepositoryImplTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - whenever(controller.addUsersFromLockScreen).thenReturn(MutableStateFlow(false)) + whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false)) whenever(controller.isGuestUserAutoCreated).thenReturn(false) whenever(controller.isGuestUserResetting).thenReturn(false) diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index bde9c3dfd641..a6e1a3256cb6 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -22,6 +22,7 @@ import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN; import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS; import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL; import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE; +import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT; import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS; import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN; import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS; @@ -185,7 +186,7 @@ final class HotwordDetectionConnection { final int mUser; final Context mContext; - @Nullable final AttentionManagerInternal mAttentionManagerInternal; + @Nullable AttentionManagerInternal mAttentionManagerInternal = null; final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal = this::setProximityMeters; @@ -240,9 +241,11 @@ final class HotwordDetectionConnection { mServiceConnectionFactory = new ServiceConnectionFactory(intent, bindInstantServiceAllowed); mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked(); - mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class); - if (mAttentionManagerInternal != null) { - mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal); + if (ENABLE_PROXIMITY_RESULT) { + mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class); + if (mAttentionManagerInternal != null) { + mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal); + } } mLastRestartInstant = Instant.now(); diff --git a/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java b/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java index 2e515705a253..761efe4a8484 100644 --- a/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java +++ b/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java @@ -110,7 +110,7 @@ public class FixVibrateSetting extends Activity implements View.OnClickListener private void test() { Intent intent = new Intent(this, FixVibrateSetting.class); - PendingIntent pending = PendingIntent.getActivity(this, 0, intent, 0); + PendingIntent pending = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); Notification n = new Notification.Builder(this) .setSmallIcon(R.drawable.stat_sys_warning) |