summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/animation/AnimationHandler.java59
-rw-r--r--core/java/android/hardware/display/DisplayManager.java12
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java12
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl2
-rw-r--r--core/java/android/view/ViewRootImpl.java13
-rw-r--r--core/java/android/window/StartingWindowRemovalInfo.java9
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl3
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java79
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java105
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java97
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java2
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt24
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt2
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml2
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml2
-rw-r--r--packages/SystemUI/res/values-sw600dp/config.xml3
-rw-r--r--packages/SystemUI/res/values/config.xml2
-rw-r--r--packages/SystemUI/res/xml/combined_qs_header_scene.xml17
-rw-r--r--packages/SystemUI/res/xml/qqs_header.xml3
-rw-r--r--packages/SystemUI/res/xml/qs_header.xml2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt11
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java47
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java18
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java127
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt180
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt95
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java261
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt224
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt101
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java28
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java63
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceService.java7
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java45
-rw-r--r--services/core/java/com/android/server/display/DisplayModeDirector.java9
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java35
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerState.java11
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java89
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java101
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java9
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java9
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java3
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java9
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java6
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java9
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java21
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java6
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java185
-rw-r--r--services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java35
113 files changed, 2286 insertions, 1040 deletions
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java
index dcabf57cb8a9..df8a50a1fa5c 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -19,10 +19,10 @@ package android.animation;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Log;
import android.view.Choreographer;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
@@ -78,7 +78,7 @@ public class AnimationHandler {
* store visible (foreground) requestors; if the set size reaches zero, there are no
* objects in the foreground and it is time to pause animators.
*/
- private final ArraySet<Object> mAnimatorRequestors = new ArraySet<>();
+ private final ArrayList<WeakReference<Object>> mAnimatorRequestors = new ArrayList<>();
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
@@ -141,19 +141,9 @@ public class AnimationHandler {
* tracking obsolete+enabled requestors.
*/
public static void removeRequestor(Object requestor) {
- getInstance().removeRequestorImpl(requestor);
- }
-
- private void removeRequestorImpl(Object requestor) {
- // Also request disablement, in case that requestor was the sole object keeping
- // animators un-paused
- requestAnimatorsEnabled(false, requestor);
- mAnimatorRequestors.remove(requestor);
+ getInstance().requestAnimatorsEnabledImpl(false, requestor);
if (LOCAL_LOGV) {
- Log.v(TAG, "removeRequestorImpl for " + requestor);
- for (int i = 0; i < mAnimatorRequestors.size(); ++i) {
- Log.v(TAG, "animatorRequesters " + i + " = " + mAnimatorRequestors.valueAt(i));
- }
+ Log.v(TAG, "removeRequestor for " + requestor);
}
}
@@ -173,10 +163,36 @@ public class AnimationHandler {
private void requestAnimatorsEnabledImpl(boolean enable, Object requestor) {
boolean wasEmpty = mAnimatorRequestors.isEmpty();
setAnimatorPausingEnabled(isPauseBgAnimationsEnabledInSystemProperties());
- if (enable) {
- mAnimatorRequestors.add(requestor);
- } else {
- mAnimatorRequestors.remove(requestor);
+ synchronized (mAnimatorRequestors) {
+ // Only store WeakRef objects to avoid leaks
+ if (enable) {
+ // First, check whether such a reference is already on the list
+ WeakReference<Object> weakRef = null;
+ for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) {
+ WeakReference<Object> ref = mAnimatorRequestors.get(i);
+ Object referent = ref.get();
+ if (referent == requestor) {
+ weakRef = ref;
+ } else if (referent == null) {
+ // Remove any reference that has been cleared
+ mAnimatorRequestors.remove(i);
+ }
+ }
+ if (weakRef == null) {
+ weakRef = new WeakReference<>(requestor);
+ mAnimatorRequestors.add(weakRef);
+ }
+ } else {
+ for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) {
+ WeakReference<Object> ref = mAnimatorRequestors.get(i);
+ Object referent = ref.get();
+ if (referent == requestor || referent == null) {
+ // remove requested item or item that has been cleared
+ mAnimatorRequestors.remove(i);
+ }
+ }
+ // If a reference to the requestor wasn't in the list, nothing to remove
+ }
}
if (!sAnimatorPausingEnabled) {
// Resume any animators that have been paused in the meantime, otherwise noop
@@ -198,9 +214,12 @@ public class AnimationHandler {
}
}
if (LOCAL_LOGV) {
- Log.v(TAG, enable ? "enable" : "disable" + " animators for " + requestor);
+ Log.v(TAG, (enable ? "enable" : "disable") + " animators for " + requestor
+ + " with pauseDelay of " + Animator.getBackgroundPauseDelay());
for (int i = 0; i < mAnimatorRequestors.size(); ++i) {
- Log.v(TAG, "animatorRequesters " + i + " = " + mAnimatorRequestors.valueAt(i));
+ Log.v(TAG, "animatorRequestors " + i + " = "
+ + mAnimatorRequestors.get(i) + " with referent "
+ + mAnimatorRequestors.get(i).get());
}
}
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 8bc11cbc61de..f94e0313771e 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -559,18 +559,20 @@ public final class DisplayManager {
* @see #DISPLAY_CATEGORY_PRESENTATION
*/
public Display[] getDisplays(String category) {
- final int[] displayIds = mGlobal.getDisplayIds();
+ boolean includeDisabled = (category != null
+ && category.equals(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED));
+ final int[] displayIds = mGlobal.getDisplayIds(includeDisabled);
synchronized (mLock) {
try {
- if (category == null
- || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) {
- addAllDisplaysLocked(mTempDisplays, displayIds);
- } else if (category.equals(DISPLAY_CATEGORY_PRESENTATION)) {
+ if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) {
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI);
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_EXTERNAL);
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY);
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL);
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_INTERNAL);
+ } else if (category == null
+ || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) {
+ addAllDisplaysLocked(mTempDisplays, displayIds);
}
return mTempDisplays.toArray(new Display[mTempDisplays.size()]);
} finally {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 74356ddecc76..63dc7c7ed661 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -206,6 +206,16 @@ public final class DisplayManagerGlobal {
*/
@UnsupportedAppUsage
public int[] getDisplayIds() {
+ return getDisplayIds(/* includeDisabled= */ false);
+ }
+
+ /**
+ * Gets all currently valid logical display ids.
+ *
+ * @param includeDisabled True if the returned list of displays includes disabled displays.
+ * @return An array containing all display ids.
+ */
+ public int[] getDisplayIds(boolean includeDisabled) {
try {
synchronized (mLock) {
if (USE_CACHE) {
@@ -214,7 +224,7 @@ public final class DisplayManagerGlobal {
}
}
- int[] displayIds = mDm.getDisplayIds();
+ int[] displayIds = mDm.getDisplayIds(includeDisabled);
if (USE_CACHE) {
mDisplayIdCache = displayIds;
}
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index ca3e58094400..a4115d178f6f 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -36,7 +36,7 @@ import android.view.Surface;
interface IDisplayManager {
@UnsupportedAppUsage
DisplayInfo getDisplayInfo(int displayId);
- int[] getDisplayIds();
+ int[] getDisplayIds(boolean includeDisabled);
boolean isUidPresentOnDisplay(int uid, int displayId);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8f4a836b6861..b4698dee6019 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1409,7 +1409,11 @@ public final class ViewRootImpl implements ViewParent,
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
- AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+ if (!mRemoved || !mAppVisible) {
+ AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+ } else if (LOCAL_LOGV) {
+ Log.v(mTag, "setView() enabling visibility when removed");
+ }
}
}
}
@@ -1747,7 +1751,12 @@ public final class ViewRootImpl implements ViewParent,
if (!mAppVisible) {
WindowManagerGlobal.trimForeground();
}
- AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+ // Only enable if the window is not already removed (via earlier call to doDie())
+ if (!mRemoved || !mAppVisible) {
+ AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+ } else if (LOCAL_LOGV) {
+ Log.v(mTag, "handleAppVisibility() enabling visibility when removed");
+ }
}
}
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 573db0d58625..384dacfe89ed 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -61,6 +61,12 @@ public final class StartingWindowRemovalInfo implements Parcelable {
*/
public boolean deferRemoveForIme;
+ /**
+ * The rounded corner radius
+ * @hide
+ */
+ public float roundedCornerRadius;
+
public StartingWindowRemovalInfo() {
}
@@ -80,6 +86,7 @@ public final class StartingWindowRemovalInfo implements Parcelable {
mainFrame = source.readTypedObject(Rect.CREATOR);
playRevealAnimation = source.readBoolean();
deferRemoveForIme = source.readBoolean();
+ roundedCornerRadius = source.readFloat();
}
@Override
@@ -89,6 +96,7 @@ public final class StartingWindowRemovalInfo implements Parcelable {
dest.writeTypedObject(mainFrame, flags);
dest.writeBoolean(playRevealAnimation);
dest.writeBoolean(deferRemoveForIme);
+ dest.writeFloat(roundedCornerRadius);
}
@Override
@@ -96,6 +104,7 @@ public final class StartingWindowRemovalInfo implements Parcelable {
return "StartingWindowRemovalInfo{taskId=" + taskId
+ " frame=" + mainFrame
+ " playRevealAnimation=" + playRevealAnimation
+ + " roundedCornerRadius=" + roundedCornerRadius
+ " deferRemoveForIme=" + deferRemoveForIme + "}";
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 56e1a872423d..2cf41bbfffc1 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -328,4 +328,7 @@ oneway interface IStatusBar
/** Shows rear display educational dialog */
void showRearDisplayDialog(int currentBaseState);
+
+ /** Called when requested to go to fullscreen from the active split app. */
+ void goToFullscreenFromSplit();
}
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 343e10e847fa..fe3e88611c6a 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"መተግበሪያ ከተከፈለ ማያ ገጽ ጋር ላይሠራ ይችላል"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም።"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ይህ መተግበሪያ መከፈት የሚችለው በ1 መስኮት ብቻ ነው።"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"መተግበሪያ በሁለተኛ ማሳያ ላይ ላይሠራ ይችላል።"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"መተግበሪያ በሁለተኛ ማሳያዎች ላይ ማስጀመርን አይደግፍም።"</string>
<string name="accessibility_divider" msgid="703810061635792791">"የተከፈለ የማያ ገጽ ከፋይ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index bf7b638587f8..0f74aab78924 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"إظهار"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"قد لا يعمل التطبيق بشكل سليم في وضع \"تقسيم الشاشة\"."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"التطبيق لا يتيح تقسيم الشاشة."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"لا يمكن فتح هذا التطبيق إلا في نافذة واحدة."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"قد لا يعمل التطبيق على شاشة عرض ثانوية."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"لا يمكن تشغيل التطبيق على شاشات عرض ثانوية."</string>
<string name="accessibility_divider" msgid="703810061635792791">"أداة تقسيم الشاشة"</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index ae2bfffd7f24..9fbf0a070019 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ez gorde"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Baliteke aplikazioak ez funtzionatzea pantaila zatituan."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikazioak ez du onartzen pantaila zatitua"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Leiho bakar batean ireki daiteke aplikazioa."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Baliteke aplikazioak ez funtzionatzea bigarren mailako pantailetan."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikazioa ezin da abiarazi bigarren mailako pantailatan."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Pantaila-zatitzailea"</string>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 4bde170710f2..ad086bb1f712 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"બતાવો"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"વિભાજિત-સ્ક્રીન સાથે ઍપ કદાચ કામ ન કરે."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ઍપ્લિકેશન સ્ક્રીન-વિભાજનનું સમર્થન કરતી નથી."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"આ ઍપ માત્ર 1 વિન્ડોમાં ખોલી શકાય છે."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર કદાચ કામ ન કરે."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર લૉન્ચનું સમર્થન કરતી નથી."</string>
<string name="accessibility_divider" msgid="703810061635792791">"સ્પ્લિટ-સ્ક્રીન વિભાજક"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 6081a1cb6c60..7b2b7c5dd01e 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"မသိုဝှက်ရန်"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းဖြင့် အက်ပ်သည် အလုပ်မလုပ်ပါ။"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"အက်ပ်သည် မျက်နှာပြင်ခွဲပြရန် ပံ့ပိုးထားခြင်းမရှိပါ။"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ဤအက်ပ်ကို ဝင်းဒိုး ၁ ခုတွင်သာ ဖွင့်နိုင်သည်။"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ဤအက်ပ်အနေဖြင့် ဒုတိယဖန်သားပြင်ပေါ်တွင် အလုပ်လုပ်မည် မဟုတ်ပါ။"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ဤအက်ပ်အနေဖြင့် ဖွင့်ရန်စနစ်ကို ဒုတိယဖန်သားပြင်မှ အသုံးပြုရန် ပံ့ပိုးမထားပါ။"</string>
<string name="accessibility_divider" msgid="703810061635792791">"မျက်နှာပြင်ခွဲခြမ်း ပိုင်းခြားပေးသည့်စနစ်"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 0a77c64bf6ed..8edcddff14e2 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 0a77c64bf6ed..8edcddff14e2 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 2f734250438a..fee34eb28725 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Fichua"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Huenda programu isifanye kazi kwenye skrini inayogawanywa."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programu haiwezi kutumia skrini iliyogawanywa."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Programu hii inaweza kufunguliwa katika dirisha 1 pekee."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Huenda programu isifanye kazi kwenye dirisha lingine."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programu hii haiwezi kufunguliwa kwenye madirisha mengine."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Kitenganishi cha skrini inayogawanywa"</string>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 2650b761e7c1..166041d6d6d8 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показати"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Додаток може не працювати в режимі розділеного екрана."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Додаток не підтримує розділення екрана."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Цей додаток можна відкрити лише в одному вікні."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Додаток може не працювати на додатковому екрані."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Додаток не підтримує запуск на додаткових екранах."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Розділювач екрана"</string>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index abc4024bc290..17ba246910d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -64,6 +64,7 @@ import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -258,18 +259,36 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
@NonNull
private WindowContainerTransaction bringDesktopAppsToFront() {
- ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
- ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
+
+ final List<RunningTaskInfo> taskInfos = new ArrayList<>();
for (Integer taskId : activeTasks) {
RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
if (taskInfo != null) {
taskInfos.add(taskInfo);
}
}
- // Order by lastActiveTime, descending
- taskInfos.sort(Comparator.comparingLong(task -> -task.lastActiveTime));
- WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ if (taskInfos.isEmpty()) {
+ return wct;
+ }
+
+ final boolean allActiveTasksAreVisible = taskInfos.stream()
+ .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId));
+ if (allActiveTasksAreVisible) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "bringDesktopAppsToFront: active tasks are already in front, skipping.");
+ return wct;
+ }
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "bringDesktopAppsToFront: reordering all active tasks to the front");
+ final List<Integer> allTasksInZOrder =
+ mDesktopModeTaskRepository.getFreeformTasksInZOrder();
+ // Sort by z-order, bottom to top, so that the top-most task is reordered to the top last
+ // in the WCT.
+ taskInfos.sort(Comparator.comparingInt(task -> -allTasksInZOrder.indexOf(task.taskId)));
for (RunningTaskInfo task : taskInfos) {
wct.reorder(task.token, true);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index b7749fc4c3d4..600ccc17ecaa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -33,6 +33,8 @@ class DesktopModeTaskRepository {
*/
private val activeTasks = ArraySet<Int>()
private val visibleTasks = ArraySet<Int>()
+ // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
+ private val freeformTasksInZOrder = mutableListOf<Int>()
private val activeTasksListeners = ArraySet<ActiveTasksListener>()
// Track visible tasks separately because a task may be part of the desktop but not visible.
private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
@@ -101,6 +103,13 @@ class DesktopModeTaskRepository {
}
/**
+ * Whether a task is visible.
+ */
+ fun isVisibleTask(taskId: Int): Boolean {
+ return visibleTasks.contains(taskId)
+ }
+
+ /**
* Get a set of the active tasks
*/
fun getActiveTasks(): ArraySet<Int> {
@@ -108,6 +117,13 @@ class DesktopModeTaskRepository {
}
/**
+ * Get a list of freeform tasks, ordered from top-bottom (top at index 0).
+ */
+ fun getFreeformTasksInZOrder(): List<Int> {
+ return freeformTasksInZOrder
+ }
+
+ /**
* Updates whether a freeform task with this id is visible or not and notifies listeners.
*/
fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) {
@@ -127,6 +143,23 @@ class DesktopModeTaskRepository {
}
/**
+ * Add (or move if it already exists) the task to the top of the ordered list.
+ */
+ fun addOrMoveFreeformTaskToTop(taskId: Int) {
+ if (freeformTasksInZOrder.contains(taskId)) {
+ freeformTasksInZOrder.remove(taskId)
+ }
+ freeformTasksInZOrder.add(0, taskId)
+ }
+
+ /**
+ * Remove the task from the ordered list.
+ */
+ fun removeFreeformTask(taskId: Int) {
+ freeformTasksInZOrder.remove(taskId)
+ }
+
+ /**
* Defines interface for classes that can listen to changes for active tasks in desktop mode.
*/
interface ActiveTasksListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 44bcdb2d5de5..8a9b74fd72b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -38,7 +38,8 @@ import java.util.Optional;
* {@link ShellTaskOrganizer.TaskListener} for {@link
* ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
*/
-public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
+public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
+ ShellTaskOrganizer.FocusListener {
private static final String TAG = "FreeformTaskListener";
private final ShellTaskOrganizer mShellTaskOrganizer;
@@ -67,6 +68,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
private void onInit() {
mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
+ if (DesktopModeStatus.IS_SUPPORTED) {
+ mShellTaskOrganizer.addFocusListener(this);
+ }
}
@Override
@@ -86,13 +90,16 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
t.apply();
}
- if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) {
+ if (DesktopModeStatus.IS_SUPPORTED) {
mDesktopModeTaskRepository.ifPresent(repository -> {
- if (repository.addActiveTask(taskInfo.taskId)) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
+ repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+ if (taskInfo.isVisible) {
+ if (repository.addActiveTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Adding active freeform task: #%d", taskInfo.taskId);
+ }
+ repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
}
- repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
});
}
}
@@ -105,6 +112,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
if (DesktopModeStatus.IS_SUPPORTED) {
mDesktopModeTaskRepository.ifPresent(repository -> {
+ repository.removeFreeformTask(taskInfo.taskId);
if (repository.removeActiveTask(taskInfo.taskId)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"Removing active freeform task: #%d", taskInfo.taskId);
@@ -140,6 +148,18 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
}
@Override
+ public void onFocusTaskChanged(RunningTaskInfo taskInfo) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
+ "Freeform Task Focus Changed: #%d focused=%b",
+ taskInfo.taskId, taskInfo.isFocused);
+ if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isFocused) {
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+ });
+ }
+ }
+
+ @Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
b.setParent(findTaskSurface(taskId));
}
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 f9172ba183de..db0f0bf6fda8 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
@@ -342,6 +342,16 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
/**
+ * Returns the top running leaf task.
+ */
+ @Nullable
+ public ActivityManager.RunningTaskInfo getTopRunningTask() {
+ List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(1,
+ false /* filterOnlyVisibleRecents */);
+ return tasks.isEmpty() ? null : tasks.get(0);
+ }
+
+ /**
* Find the background task that match the given component.
*/
@Nullable
@@ -367,6 +377,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
+ pw.println(prefix + " mListener=" + mListener);
+ pw.println(prefix + "Tasks:");
ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
for (int i = 0; i < recentTasks.size(); i++) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index d86aadc996e3..2f2bc77b804b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -73,6 +73,9 @@ public interface SplitScreen {
/** Called when device waking up finished. */
void onFinishedWakingUp();
+ /** Called when requested to go to fullscreen from the current active split app. */
+ void goToFullscreenFromSplit();
+
/** Get a string representation of a stage type */
static String stageTypeToString(@StageType int stage) {
switch (stage) {
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 a79ac45228ca..400039b32618 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
@@ -123,6 +123,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public static final int EXIT_REASON_SCREEN_LOCKED = 7;
public static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
+ public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 10;
@IntDef(value = {
EXIT_REASON_UNKNOWN,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -134,6 +135,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
EXIT_REASON_SCREEN_LOCKED,
EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP,
EXIT_REASON_CHILD_TASK_ENTER_PIP,
+ EXIT_REASON_FULLSCREEN_SHORTCUT,
})
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
@@ -315,10 +317,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mStageCoordinator;
}
- public ActivityManager.RunningTaskInfo getFocusingTaskInfo() {
- return mStageCoordinator.getFocusingTaskInfo();
- }
-
public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
return mStageCoordinator.isValidToEnterSplitScreen(taskInfo);
}
@@ -422,6 +420,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.unregisterSplitScreenListener(listener);
}
+ public void goToFullscreenFromSplit() {
+ mStageCoordinator.goToFullscreenFromSplit();
+ }
+
public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -628,9 +630,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
if (!isSplitScreenVisible()) {
// Split screen is not yet activated, check if the current top running task is valid to
// split together.
- final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
- if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
- return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+ final ActivityManager.RunningTaskInfo topRunningTask = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.getTopRunningTask()).orElse(null);
+ if (topRunningTask != null && isValidToEnterSplitScreen(topRunningTask)) {
+ return Objects.equals(topRunningTask.baseIntent.getComponent(), launchingActivity);
}
return false;
}
@@ -863,9 +866,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void onFinishedWakingUp() {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.onFinishedWakingUp();
- });
+ mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp);
+ }
+
+ @Override
+ public void goToFullscreenFromSplit() {
+ mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit);
}
}
@@ -921,33 +927,25 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void exitSplitScreen(int toTopTaskId) {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
- (controller) -> {
- controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN);
- });
+ (controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN));
}
@Override
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
- (controller) -> {
- controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
- });
+ (controller) -> controller.exitSplitScreenOnHide(exitSplitScreenOnHide));
}
@Override
public void removeFromSideStage(int taskId) {
executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
- (controller) -> {
- controller.removeFromSideStage(taskId);
- });
+ (controller) -> controller.removeFromSideStage(taskId));
}
@Override
public void startTask(int taskId, int position, @Nullable Bundle options) {
executeRemoteCallWithTaskPermission(mController, "startTask",
- (controller) -> {
- controller.startTask(taskId, position, options);
- });
+ (controller) -> controller.startTask(taskId, position, options));
}
@Override
@@ -1039,19 +1037,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void startShortcut(String packageName, String shortcutId, int position,
@Nullable Bundle options, UserHandle user, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startShortcut",
- (controller) -> {
- controller.startShortcut(packageName, shortcutId, position, options, user,
- instanceId);
- });
+ (controller) -> controller.startShortcut(packageName, shortcutId, position,
+ options, user, instanceId));
}
@Override
public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
@Nullable Bundle options, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntent",
- (controller) -> {
- controller.startIntent(intent, fillInIntent, position, options, instanceId);
- });
+ (controller) -> controller.startIntent(intent, fillInIntent, position, options,
+ instanceId));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 4cb76230606f..aa0512b64a16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -49,6 +49,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASO
import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
@@ -150,7 +151,7 @@ import java.util.Optional;
*/
public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler,
- ShellTaskOrganizer.TaskListener, ShellTaskOrganizer.FocusListener {
+ ShellTaskOrganizer.TaskListener {
private static final String TAG = StageCoordinator.class.getSimpleName();
@@ -186,8 +187,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final Rect mTempRect1 = new Rect();
private final Rect mTempRect2 = new Rect();
- private ActivityManager.RunningTaskInfo mFocusingTaskInfo;
-
/**
* A single-top root task which the split divider attached to.
*/
@@ -304,7 +303,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayController.addDisplayWindowListener(this);
mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
transitions.addHandler(this);
- mTaskOrganizer.addFocusListener(this);
mSplitUnsupportedToast = Toast.makeText(mContext,
R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
}
@@ -455,8 +453,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
/** Launches an activity into split by legacy transition. */
- void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options) {
+ void startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
+ @Nullable Bundle options) {
+ final boolean isEnteringSplit = !isSplitActive();
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct);
@@ -466,22 +465,29 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback,
SurfaceControl.Transaction t) {
- if (apps == null || apps.length == 0) {
- if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
- mMainExecutor.execute(() ->
- exitSplitScreen(mMainStage.getChildCount() == 0
- ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
- mSplitUnsupportedToast.show();
+ if (isEnteringSplit) {
+ boolean openingToSide = false;
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING
+ && mSideStage.containsTask(apps[i].taskId)) {
+ openingToSide = true;
+ break;
+ }
+ }
+ }
+ if (!openingToSide) {
+ mMainExecutor.execute(() -> exitSplitScreen(
+ mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
+ EXIT_REASON_UNKNOWN));
}
-
- // Do nothing when the animation was cancelled.
- t.apply();
- return;
}
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) {
- t.show(apps[i].leash);
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
}
}
t.apply();
@@ -503,7 +509,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// If split still not active, apply windows bounds first to avoid surface reset to
// wrong pos by SurfaceAnimator from wms.
- if (!mMainStage.isActive() && mLogger.isEnterRequestedByDrag()) {
+ if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) {
updateWindowBounds(mSplitLayout, wct);
}
@@ -1110,15 +1116,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* Exits the split screen by finishing one of the tasks.
*/
protected void exitStage(@SplitPosition int stageToClose) {
- if (ENABLE_SHELL_TRANSITIONS) {
- StageTaskListener stageToTop = mSideStagePosition == stageToClose
- ? mMainStage
- : mSideStage;
- exitSplitScreen(stageToTop, EXIT_REASON_APP_FINISHED);
- } else {
- boolean toEnd = stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT;
- mSplitLayout.flingDividerToDismiss(toEnd, EXIT_REASON_APP_FINISHED);
- }
+ mSplitLayout.flingDividerToDismiss(stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ EXIT_REASON_APP_FINISHED);
}
/**
@@ -1152,6 +1151,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
// User has unlocked the device after folded
case EXIT_REASON_DEVICE_FOLDED:
+ // The device is folded
+ case EXIT_REASON_FULLSCREEN_SHORTCUT:
+ // User has used a keyboard shortcut to go back to fullscreen from split
return true;
default:
return false;
@@ -1615,15 +1617,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
&& ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
}
- ActivityManager.RunningTaskInfo getFocusingTaskInfo() {
- return mFocusingTaskInfo;
- }
-
- @Override
- public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
- mFocusingTaskInfo = taskInfo;
- }
-
@Override
public void onSnappedToDismiss(boolean bottomOrRight, int reason) {
final boolean mainStageToTop =
@@ -2123,6 +2116,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
+ public void goToFullscreenFromSplit() {
+ boolean leftOrTop;
+ if (mSideStage.isFocused()) {
+ leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ } else {
+ leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ }
+ mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+ }
+
/** Synchronize split-screen state with transition and make appropriate preparations. */
public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 8bba44049c88..20da8773f387 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -50,13 +50,17 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
private final float mIconStartAlpha;
private final float mBrandingStartAlpha;
private final TransactionPool mTransactionPool;
+ // TODO(b/261167708): Clean enter animation code after moving Letterbox code to Shell
+ private final float mRoundedCornerRadius;
private Runnable mFinishCallback;
SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
- Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish) {
+ Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish,
+ float roundedCornerRadius) {
mSplashScreenView = view;
mFirstWindowSurface = leash;
+ mRoundedCornerRadius = roundedCornerRadius;
if (frame != null) {
mFirstWindowFrame.set(frame);
}
@@ -97,7 +101,7 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
- mAppRevealDuration, this);
+ mAppRevealDuration, this, mRoundedCornerRadius);
}
private void reset() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index 3098e55ec78b..a7e4385b60c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -63,23 +63,39 @@ public class SplashScreenExitAnimationUtils {
/**
* Creates and starts the animator to fade out the icon, reveal the app, and shift up main
- * window.
- * @hide
+ * window with rounded corner radius.
*/
- public static void startAnimations(ViewGroup splashScreenView,
+ static void startAnimations(ViewGroup splashScreenView,
SurfaceControl firstWindowSurface, int mainWindowShiftLength,
TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
- int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
+ float roundedCornerRadius) {
ValueAnimator animator =
createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
- animatorListener);
+ animatorListener, roundedCornerRadius);
animator.start();
}
/**
+ * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
+ * window.
+ * @hide
+ */
+ public static void startAnimations(ViewGroup splashScreenView,
+ SurfaceControl firstWindowSurface, int mainWindowShiftLength,
+ TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+ int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+ startAnimations(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+ transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+ iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+ animatorListener, 0f /* roundedCornerRadius */);
+ }
+
+ /**
* Creates the animator to fade out the icon, reveal the app, and shift up main window.
* @hide
*/
@@ -87,7 +103,8 @@ public class SplashScreenExitAnimationUtils {
SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
- int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
+ float roundedCornerRadius) {
// reveal app
final float transparentRatio = 0.8f;
final int globalHeight = splashScreenView.getHeight();
@@ -124,7 +141,7 @@ public class SplashScreenExitAnimationUtils {
shiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView,
firstWindowSurface, splashScreenView, transactionPool, firstWindowFrame,
- mMainWindowShiftLength);
+ mMainWindowShiftLength, roundedCornerRadius);
}
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
@@ -289,8 +306,8 @@ public class SplashScreenExitAnimationUtils {
public ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView,
SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
TransactionPool transactionPool, Rect firstWindowFrame,
- int mainWindowShiftLength) {
- mFromYDelta = fromYDelta;
+ int mainWindowShiftLength, float roundedCornerRadius) {
+ mFromYDelta = fromYDelta - roundedCornerRadius;
mToYDelta = toYDelta;
mOccludeHoleView = occludeHoleView;
mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 6ce981e25f5e..839d56a43222 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -993,10 +993,11 @@ public class SplashscreenContentDrawer {
* Create and play the default exit animation for splash screen view.
*/
void applyExitAnimation(SplashScreenView view, SurfaceControl leash,
- Rect frame, Runnable finishCallback, long createTime) {
+ Rect frame, Runnable finishCallback, long createTime, float roundedCornerRadius) {
final Runnable playAnimation = () -> {
final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext,
- view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback);
+ view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback,
+ roundedCornerRadius);
animation.startAnimations();
};
if (view.getIconView() == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index a0e176c7ea68..053491e3a3ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -645,7 +645,7 @@ public class StartingSurfaceDrawer {
mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
removalInfo.windowAnimationLeash, removalInfo.mainFrame,
() -> removeWindowInner(record.mDecorView, true),
- record.mCreateTime);
+ record.mCreateTime, removalInfo.roundedCornerRadius);
} else {
// the SplashScreenView has been copied to client, hide the view to skip
// default exit animation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index e40db4e4dcf2..e7036c726a27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -75,10 +75,10 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final SyncTransactionQueue mSyncQueue;
private FreeformTaskTransitionStarter mTransitionStarter;
private DesktopModeController mDesktopModeController;
- private EventReceiver mEventReceiver;
- private InputMonitor mInputMonitor;
private boolean mTransitionDragActive;
+ private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
+
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory();
@@ -150,8 +150,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+
if (decoration == null) return;
+ int oldDisplayId = decoration.mDisplay.getDisplayId();
+ if (taskInfo.displayId != oldDisplayId) {
+ removeTaskFromEventReceiver(oldDisplayId);
+ incrementEventReceiverTasks(taskInfo.displayId);
+ }
+
decoration.relayout(taskInfo);
}
@@ -195,6 +202,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
if (decoration == null) return;
decoration.close();
+ int displayId = taskInfo.displayId;
+ if (mEventReceiversByDisplay.contains(displayId)) {
+ EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ removeTaskFromEventReceiver(displayId);
+ }
}
private class CaptionTouchEventListener implements
@@ -329,8 +341,12 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
// InputEventReceiver to listen for touch input outside of caption bounds
class EventReceiver extends InputEventReceiver {
- EventReceiver(InputChannel channel, Looper looper) {
+ private InputMonitor mInputMonitor;
+ private int mTasksOnDisplay;
+ EventReceiver(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
super(channel, looper);
+ mInputMonitor = inputMonitor;
+ mTasksOnDisplay = 1;
}
@Override
@@ -338,15 +354,62 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
boolean handled = false;
if (event instanceof MotionEvent) {
handled = true;
- CaptionWindowDecorViewModel.this.handleReceivedMotionEvent((MotionEvent) event);
+ CaptionWindowDecorViewModel.this
+ .handleReceivedMotionEvent((MotionEvent) event, mInputMonitor);
}
finishInputEvent(event, handled);
}
+
+ @Override
+ public void dispose() {
+ if (mInputMonitor != null) {
+ mInputMonitor.dispose();
+ mInputMonitor = null;
+ }
+ super.dispose();
+ }
+
+ private void incrementTaskNumber() {
+ mTasksOnDisplay++;
+ }
+
+ private void decrementTaskNumber() {
+ mTasksOnDisplay--;
+ }
+
+ private int getTasksOnDisplay() {
+ return mTasksOnDisplay;
+ }
+ }
+
+ /**
+ * Check if an EventReceiver exists on a particular display.
+ * If it does, increment its task count. Otherwise, create one for that display.
+ * @param displayId the display to check against
+ */
+ private void incrementEventReceiverTasks(int displayId) {
+ if (mEventReceiversByDisplay.contains(displayId)) {
+ EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ eventReceiver.incrementTaskNumber();
+ } else {
+ createInputChannel(displayId);
+ }
+ }
+
+ // If all tasks on this display are gone, we don't need to monitor its input.
+ private void removeTaskFromEventReceiver(int displayId) {
+ if (!mEventReceiversByDisplay.contains(displayId)) return;
+ EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ if (eventReceiver == null) return;
+ eventReceiver.decrementTaskNumber();
+ if (eventReceiver.getTasksOnDisplay() == 0) {
+ disposeInputChannel(displayId);
+ }
}
class EventReceiverFactory {
- EventReceiver create(InputChannel channel, Looper looper) {
- return new EventReceiver(channel, looper);
+ EventReceiver create(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
+ return new EventReceiver(inputMonitor, channel, looper);
}
}
@@ -355,14 +418,14 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
*
* @param ev the {@link MotionEvent} received by {@link EventReceiver}
*/
- private void handleReceivedMotionEvent(MotionEvent ev) {
+ private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
if (!DesktopModeStatus.isActive(mContext)) {
handleCaptionThroughStatusBar(ev);
}
handleEventOutsideFocusedCaption(ev);
// Prevent status bar from reacting to a caption drag.
if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
- mInputMonitor.pilferPointers();
+ inputMonitor.pilferPointers();
}
}
@@ -381,6 +444,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
}
}
+
/**
* Perform caption actions if not able to through normal means.
* Turn on desktop mode if handle is dragged below status bar.
@@ -434,6 +498,22 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
return focusedDecor;
}
+ private void createInputChannel(int displayId) {
+ InputManager inputManager = mInputManagerSupplier.get();
+ InputMonitor inputMonitor =
+ inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
+ EventReceiver eventReceiver = mEventReceiverFactory.create(
+ inputMonitor, inputMonitor.getInputChannel(), Looper.myLooper());
+ mEventReceiversByDisplay.put(displayId, eventReceiver);
+ }
+
+ private void disposeInputChannel(int displayId) {
+ EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId);
+ if (eventReceiver != null) {
+ eventReceiver.dispose();
+ }
+ }
+
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
return DesktopModeStatus.IS_SUPPORTED
@@ -472,14 +552,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
windowDecoration.relayout(taskInfo, startT, finishT);
- if (mInputMonitor == null) {
- InputManager inputManager = mInputManagerSupplier.get();
- mInputMonitor =
- inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
- mEventReceiver =
- mEventReceiverFactory.create(
- mInputMonitor.getInputChannel(), Looper.myLooper());
- }
+ incrementEventReceiverTasks(taskInfo.displayId);
}
private class DragStartListenerImpl implements TaskPositioner.DragStartListener {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 89bafcb6b2f4..01584a067cc2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -50,6 +50,7 @@ import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransaction.Change;
+import android.window.WindowContainerTransaction.HierarchyOp;
import androidx.test.filters.SmallTest;
@@ -222,25 +223,29 @@ public class DesktopModeControllerTest extends ShellTestCase {
// Check that there are hierarchy changes for home task and visible task
assertThat(wct.getHierarchyOps()).hasSize(2);
// First show home task
- WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ HierarchyOp op1 = wct.getHierarchyOps().get(0);
assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder());
// Then visible task on top of it
- WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ HierarchyOp op2 = wct.getHierarchyOps().get(1);
assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder());
}
@Test
- public void testShowDesktopApps() {
- // Set up two active tasks on desktop
+ public void testShowDesktopApps_allAppsInvisible_bringsToFront() {
+ // Set up two active tasks on desktop, task2 is on top of task1.
RunningTaskInfo freeformTask1 = createFreeformTask();
- freeformTask1.lastActiveTime = 100;
- RunningTaskInfo freeformTask2 = createFreeformTask();
- freeformTask2.lastActiveTime = 200;
mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+ freeformTask1.taskId, false /* visible */);
+ RunningTaskInfo freeformTask2 = createFreeformTask();
mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+ freeformTask2.taskId, false /* visible */);
when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
freeformTask1);
when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
@@ -248,27 +253,66 @@ public class DesktopModeControllerTest extends ShellTestCase {
// Run show desktop apps logic
mController.showDesktopApps();
- ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), wctCaptor.capture(), any());
- } else {
- verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
- }
- WindowContainerTransaction wct = wctCaptor.getValue();
+ final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
// Check wct has reorder calls
assertThat(wct.getHierarchyOps()).hasSize(2);
- // Task 2 has activity later, must be first
- WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ // Task 1 appeared first, must be first reorder to top.
+ HierarchyOp op1 = wct.getHierarchyOps().get(0);
assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(freeformTask2.token.asBinder());
+ assertThat(op1.getContainer()).isEqualTo(freeformTask1.token.asBinder());
+
+ // Task 2 appeared last, must be last reorder to top.
+ HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op2.getContainer()).isEqualTo(freeformTask2.token.asBinder());
+ }
+
+ @Test
+ public void testShowDesktopApps_appsAlreadyVisible_doesNothing() {
+ final RunningTaskInfo task1 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
+ final RunningTaskInfo task2 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
+
+ mController.showDesktopApps();
+
+ final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+ // No reordering needed.
+ assertThat(wct.getHierarchyOps()).isEmpty();
+ }
+
+ @Test
+ public void testShowDesktopApps_someAppsInvisible_reordersAll() {
+ final RunningTaskInfo task1 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, false /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
+ final RunningTaskInfo task2 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
- // Task 1 should be second
- WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ mController.showDesktopApps();
+
+ final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+ // Both tasks should be reordered to top, even if one was already visible.
+ assertThat(wct.getHierarchyOps()).hasSize(2);
+ final HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
+ final HierarchyOp op2 = wct.getHierarchyOps().get(1);
assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(freeformTask1.token.asBinder());
+ assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
}
@Test
@@ -355,6 +399,17 @@ public class DesktopModeControllerTest extends ShellTestCase {
return arg.getValue();
}
+ private WindowContainerTransaction getBringAppsToFrontTransaction() {
+ final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), arg.capture(), any());
+ } else {
+ verify(mShellTaskOrganizer).applyTransaction(arg.capture());
+ }
+ return arg.getValue();
+ }
+
private void assertThatBoundsCleared(Change change) {
assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue();
assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index aaa5c8a35acb..1e43a5983821 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -140,6 +140,32 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3)
}
+ @Test
+ fun addOrMoveFreeformTaskToTop_didNotExist_addsToTop() {
+ repo.addOrMoveFreeformTaskToTop(5)
+ repo.addOrMoveFreeformTaskToTop(6)
+ repo.addOrMoveFreeformTaskToTop(7)
+
+ val tasks = repo.getFreeformTasksInZOrder()
+ assertThat(tasks.size).isEqualTo(3)
+ assertThat(tasks[0]).isEqualTo(7)
+ assertThat(tasks[1]).isEqualTo(6)
+ assertThat(tasks[2]).isEqualTo(5)
+ }
+
+ @Test
+ fun addOrMoveFreeformTaskToTop_alreadyExists_movesToTop() {
+ repo.addOrMoveFreeformTaskToTop(5)
+ repo.addOrMoveFreeformTaskToTop(6)
+ repo.addOrMoveFreeformTaskToTop(7)
+
+ repo.addOrMoveFreeformTaskToTop(6)
+
+ val tasks = repo.getFreeformTasksInZOrder()
+ assertThat(tasks.size).isEqualTo(3)
+ assertThat(tasks.first()).isEqualTo(6)
+ }
+
class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
var activeTaskChangedCalls = 0
override fun onActiveTasksChanged() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 38b75f81171f..f8ded7709c68 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -178,10 +178,10 @@ public class SplitScreenControllerTests extends ShellTestCase {
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
- // Put the same component into focus task
- ActivityManager.RunningTaskInfo focusTaskInfo =
+ // Put the same component to the top running task
+ ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
@@ -199,10 +199,10 @@ public class SplitScreenControllerTests extends ShellTestCase {
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
- // Put the same component into focus task
- ActivityManager.RunningTaskInfo focusTaskInfo =
+ // Put the same component to the top running task
+ ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
// Put the same component into a task in the background
ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
index 8b134ed1dfe4..9b37b97a4c24 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
@@ -111,7 +111,7 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
.create(any(), any(), any(), any(), any(), any(), any(), any());
when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor);
- when(mEventReceiverFactory.create(any(), any())).thenReturn(mEventReceiver);
+ when(mEventReceiverFactory.create(any(), any(), any())).thenReturn(mEventReceiver);
when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel);
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 22944b8fba89..462b90a10aee 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -237,6 +237,28 @@ class AnimatableClockView @JvmOverloads constructor(
this.lockScreenColor = lockScreenColor
}
+ fun animateColorChange() {
+ logBuffer?.log(tag, DEBUG, "animateColorChange")
+ setTextStyle(
+ weight = lockScreenWeight,
+ textSize = -1f,
+ color = null, /* using current color */
+ animate = false,
+ duration = 0,
+ delay = 0,
+ onAnimationEnd = null
+ )
+ setTextStyle(
+ weight = lockScreenWeight,
+ textSize = -1f,
+ color = lockScreenColor,
+ animate = true,
+ duration = COLOR_ANIM_DURATION,
+ delay = 0,
+ onAnimationEnd = null
+ )
+ }
+
fun animateAppearOnLockscreen() {
logBuffer?.log(tag, DEBUG, "animateAppearOnLockscreen")
setTextStyle(
@@ -350,6 +372,7 @@ class AnimatableClockView @JvmOverloads constructor(
*
* By passing -1 to weight, the view preserves its current weight.
* By passing -1 to textSize, the view preserves its current text size.
+ * By passing null to color, the view preserves its current color.
*
* @param weight text weight.
* @param textSize font size.
@@ -611,6 +634,7 @@ class AnimatableClockView @JvmOverloads constructor(
private const val APPEAR_ANIM_DURATION: Long = 350
private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
+ private const val COLOR_ANIM_DURATION: Long = 400
// Constants for the animation
private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index e1f21742bf93..c540f0f7d557 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -142,7 +142,7 @@ class DefaultClockController(
currentColor = color
view.setColors(DOZE_COLOR, color)
if (!animations.dozeState.isActive) {
- view.animateAppearOnLockscreen()
+ view.animateColorChange()
}
}
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
index 898935fc7e99..2cac9c706fe9 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
@@ -21,8 +21,6 @@
android:id="@+id/keyguard_bouncer_user_switcher"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:clipChildren="false"
- android:clipToPadding="false"
android:orientation="vertical"
android:gravity="center"
android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
index 411fea5dd22d..316ad39a9f05 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
@@ -18,6 +18,8 @@
<TextView
android:id="@+id/digit_text"
style="@style/Widget.TextView.NumPadKey.Digit"
+ android:autoSizeMaxTextSize="32sp"
+ android:autoSizeTextType="uniform"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 80628f903e76..f4434e8d0044 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -36,7 +36,4 @@
<integer name="qs_security_footer_maxLines">1</integer>
<bool name="config_use_large_screen_shade_header">true</bool>
-
- <!-- Whether to show the side fps hint while on bouncer -->
- <bool name="config_show_sidefps_hint_on_bouncer">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7a362040427a..4cda8c7b5328 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -552,7 +552,7 @@
<string name="config_preferredEmergencySosPackage" translatable="false"></string>
<!-- Whether to show the side fps hint while on bouncer -->
- <bool name="config_show_sidefps_hint_on_bouncer">false</bool>
+ <bool name="config_show_sidefps_hint_on_bouncer">true</bool>
<!-- Whether to use the split 2-column notification shade -->
<bool name="config_use_split_notification_shade">false</bool>
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index de855e275f5f..c32de70771d0 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -124,20 +124,9 @@
</KeyFrameSet>
</Transition>
- <Transition
- android:id="@+id/large_screen_header_transition"
- app:constraintSetStart="@id/large_screen_header_constraint"
- app:constraintSetEnd="@id/large_screen_header_constraint"/>
-
- <!--
- Placeholder ConstraintSet. They are populated in the controller for this class.
- This is needed because there's no easy way to just refer to a `ConstraintSet` file. The
- options are either a layout file or inline the ConstraintSets.
- -->
- <ConstraintSet android:id="@id/qqs_header_constraint"/>
-
- <ConstraintSet android:id="@id/qs_header_constraint"/>
+ <Include app:constraintSet="@xml/large_screen_shade_header"/>
- <ConstraintSet android:id="@id/large_screen_header_constraint" />
+ <Include app:constraintSet="@xml/qs_header"/>
+ <Include app:constraintSet="@xml/qqs_header"/>
</MotionScene>
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index 5d3650ccc8e6..e56e5d557c2f 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -59,7 +59,6 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constrainedWidth="true"
app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
@@ -75,7 +74,6 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constrainedWidth="true"
app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="@id/end_guide"
@@ -112,5 +110,4 @@
app:layout_constraintHorizontal_bias="1"
/>
</Constraint>
-
</ConstraintSet> \ No newline at end of file
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index 982c422f1fda..eca2b2acb079 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -56,6 +56,7 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constrainedWidth="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/space"
app:layout_constraintBottom_toBottomOf="parent"
@@ -88,7 +89,6 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/space"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
app:layout_constraintTop_toTopOf="@id/date"
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
index 4a41b3fe2589..5bb9367fa4a5 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
@@ -48,11 +48,13 @@ import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_INIT
import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_OCCLUSION_CHANGED
import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_RESET
import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_VISIBILITY_CHANGED
+import com.android.keyguard.InternalFaceAuthReasons.NON_STRONG_BIOMETRIC_ALLOWED_CHANGED
import com.android.keyguard.InternalFaceAuthReasons.OCCLUDING_APP_REQUESTED
import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN
import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN
import com.android.keyguard.InternalFaceAuthReasons.RETRY_AFTER_HW_UNAVAILABLE
import com.android.keyguard.InternalFaceAuthReasons.STARTED_WAKING_UP
+import com.android.keyguard.InternalFaceAuthReasons.STRONG_AUTH_ALLOWED_CHANGED
import com.android.keyguard.InternalFaceAuthReasons.TRUST_DISABLED
import com.android.keyguard.InternalFaceAuthReasons.TRUST_ENABLED
import com.android.keyguard.InternalFaceAuthReasons.USER_SWITCHING
@@ -121,6 +123,9 @@ private object InternalFaceAuthReasons {
const val FACE_AUTHENTICATED = "Face auth started/stopped because face is authenticated"
const val BIOMETRIC_ENABLED =
"Face auth started/stopped because biometric is enabled on keyguard"
+ const val STRONG_AUTH_ALLOWED_CHANGED = "Face auth stopped because strong auth allowed changed"
+ const val NON_STRONG_BIOMETRIC_ALLOWED_CHANGED =
+ "Face auth stopped because non strong biometric allowed changed"
}
/**
@@ -204,7 +209,11 @@ constructor(private val id: Int, val reason: String, var extraInfo: Int = 0) :
@UiEvent(doc = FACE_AUTHENTICATED)
FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED(1187, FACE_AUTHENTICATED),
@UiEvent(doc = BIOMETRIC_ENABLED)
- FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD(1188, BIOMETRIC_ENABLED);
+ FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD(1188, BIOMETRIC_ENABLED),
+ @UiEvent(doc = STRONG_AUTH_ALLOWED_CHANGED)
+ FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED(1255, STRONG_AUTH_ALLOWED_CHANGED),
+ @UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED)
+ FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED);
override fun getId(): Int = this.id
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index e6283b86283b..52ca1668e4c9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -53,17 +53,17 @@ data class KeyguardFaceListenModel(
val biometricSettingEnabledForUser: Boolean,
val bouncerFullyShown: Boolean,
val faceAndFpNotAuthenticated: Boolean,
+ val faceAuthAllowed: Boolean,
val faceDisabled: Boolean,
val faceLockedOut: Boolean,
- val fpLockedOut: Boolean,
val goingToSleep: Boolean,
val keyguardAwake: Boolean,
val keyguardGoingAway: Boolean,
val listeningForFaceAssistant: Boolean,
val occludingAppRequestingFaceAuth: Boolean,
val primaryUser: Boolean,
- val scanningAllowedByStrongAuth: Boolean,
val secureCameraLaunched: Boolean,
+ val supportsDetect: Boolean,
val switchingUser: Boolean,
val udfpsBouncerShowing: Boolean,
val udfpsFingerDown: Boolean,
@@ -79,9 +79,8 @@ data class KeyguardActiveUnlockModel(
// keep sorted
val awakeKeyguard: Boolean,
val authInterruptActive: Boolean,
- val encryptedOrTimedOut: Boolean,
- val fpLockout: Boolean,
- val lockDown: Boolean,
+ val fpLockedOut: Boolean,
+ val primaryAuthRequired: Boolean,
val switchingUser: Boolean,
val triggerActiveUnlockForAssistant: Boolean,
val userCanDismissLockScreen: Boolean
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 2cc5ccdc3fa1..c985fd7bef82 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -34,6 +34,7 @@ import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Trace;
import android.util.AttributeSet;
+import android.view.WindowInsets;
import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowInsetsAnimationController;
import android.view.animation.AnimationUtils;
@@ -236,4 +237,50 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
return getResources().getString(
com.android.internal.R.string.keyguard_accessibility_password_unlock);
}
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ if (!mPasswordEntry.isFocused() && isVisibleToUser()) {
+ mPasswordEntry.requestFocus();
+ }
+ return super.onApplyWindowInsets(insets);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (hasWindowFocus) {
+ if (isVisibleToUser()) {
+ showKeyboard();
+ } else {
+ hideKeyboard();
+ }
+ }
+ }
+
+ /**
+ * Sends signal to the focused window to show the keyboard.
+ */
+ public void showKeyboard() {
+ post(() -> {
+ if (mPasswordEntry.isAttachedToWindow()
+ && !mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+ mPasswordEntry.requestFocus();
+ mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime());
+ }
+ });
+ }
+
+ /**
+ * Sends signal to the focused window to hide the keyboard.
+ */
+ public void hideKeyboard() {
+ post(() -> {
+ if (mPasswordEntry.isAttachedToWindow()
+ && mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+ mPasswordEntry.clearFocus();
+ mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
+ }
+ });
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 195e8f92754d..d221e22a4fcd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -26,7 +26,6 @@ import android.text.method.TextKeyListener;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
-import android.view.WindowInsets;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
@@ -200,12 +199,9 @@ public class KeyguardPasswordViewController
return;
}
- mView.post(() -> {
- if (mView.isShown()) {
- mPasswordEntry.requestFocus();
- mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime());
- }
- });
+ if (mView.isShown()) {
+ mView.showKeyboard();
+ }
}
@Override
@@ -227,16 +223,12 @@ public class KeyguardPasswordViewController
super.onPause();
});
}
- if (mPasswordEntry.isAttachedToWindow()) {
- mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
- }
+ mView.hideKeyboard();
}
@Override
public void onStartingToHide() {
- if (mPasswordEntry.isAttachedToWindow()) {
- mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
- }
+ mView.hideKeyboard();
}
private void updateSwitchImeButton() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 5c4126eeb93a..8f3484a0c99b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -221,10 +221,11 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
public void onEnd(WindowInsetsAnimation animation) {
if (!mDisappearAnimRunning) {
endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
- updateChildren(0 /* translationY */, 1f /* alpha */);
} else {
endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
+ setAlpha(0f);
}
+ updateChildren(0 /* translationY */, 1f /* alpha */);
}
private void updateChildren(int translationY, float alpha) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 90ed293c73c9..d1a59cc334ab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -34,9 +34,9 @@ import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.keyguard.FaceAuthReasonKt.apiRequestReasonToUiEvent;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_DREAM_STARTED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP;
@@ -65,6 +65,7 @@ import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_FACE_AUT
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_KEYGUARD_INIT;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING;
import static com.android.systemui.DejankUtils.whitelistIpcs;
@@ -171,7 +172,6 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -1421,6 +1421,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
+ /**
+ * Whether the user locked down the device. This doesn't include device policy manager lockdown.
+ */
public boolean isUserInLockdown(int userId) {
return containsFlag(mStrongAuthTracker.getStrongAuthForUser(userId),
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
@@ -1452,7 +1455,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return mStrongAuthTracker;
}
- private void notifyStrongAuthStateChanged(int userId) {
+ @VisibleForTesting
+ void notifyStrongAuthAllowedChanged(int userId) {
Assert.isMainThread();
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1460,6 +1464,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
cb.onStrongAuthStateChanged(userId);
}
}
+ if (userId == getCurrentUser()) {
+ FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED.setExtraInfo(
+ mStrongAuthTracker.getStrongAuthForUser(getCurrentUser()));
+
+ // Strong auth is only reset when primary auth is used to enter the device,
+ // so we only check whether to stop biometric listening states here
+ updateBiometricListeningState(
+ BIOMETRIC_ACTION_STOP, FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED);
+ }
}
private void notifyLockedOutStateChanged(BiometricSourceType type) {
@@ -1471,8 +1484,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
}
-
- private void notifyNonStrongBiometricStateChanged(int userId) {
+ @VisibleForTesting
+ void notifyNonStrongBiometricAllowedChanged(int userId) {
Assert.isMainThread();
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1480,6 +1493,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
cb.onNonStrongBiometricAllowedChanged(userId);
}
}
+ if (userId == getCurrentUser()) {
+ FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED.setExtraInfo(
+ mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(
+ getCurrentUser()) ? -1 : 1);
+
+ // This is only reset when primary auth is used to enter the device, so we only check
+ // whether to stop biometric listening states here
+ updateBiometricListeningState(BIOMETRIC_ACTION_STOP,
+ FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED);
+ }
}
private void dispatchErrorMessage(CharSequence message) {
@@ -1825,16 +1848,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
- public static class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
- private final Consumer<Integer> mStrongAuthRequiredChangedCallback;
- private final Consumer<Integer> mNonStrongBiometricAllowedChanged;
-
- public StrongAuthTracker(Context context,
- Consumer<Integer> strongAuthRequiredChangedCallback,
- Consumer<Integer> nonStrongBiometricAllowedChanged) {
+ /**
+ * Updates callbacks when strong auth requirements change.
+ */
+ public class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+ public StrongAuthTracker(Context context) {
super(context);
- mStrongAuthRequiredChangedCallback = strongAuthRequiredChangedCallback;
- mNonStrongBiometricAllowedChanged = nonStrongBiometricAllowedChanged;
}
public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
@@ -1850,7 +1869,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Override
public void onStrongAuthRequiredChanged(int userId) {
- mStrongAuthRequiredChangedCallback.accept(userId);
+ notifyStrongAuthAllowedChanged(userId);
}
// TODO(b/247091681): Renaming the inappropriate onIsNonStrongBiometricAllowedChanged
@@ -1858,7 +1877,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// Strong-Auth
@Override
public void onIsNonStrongBiometricAllowedChanged(int userId) {
- mNonStrongBiometricAllowedChanged.accept(userId);
+ notifyNonStrongBiometricAllowedChanged(userId);
}
}
@@ -2019,8 +2038,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mUserTracker = userTracker;
mTelephonyListenerManager = telephonyListenerManager;
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
- mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged,
- this::notifyNonStrongBiometricStateChanged);
+ mStrongAuthTracker = new StrongAuthTracker(context);
mBackgroundExecutor = backgroundExecutor;
mBroadcastDispatcher = broadcastDispatcher;
mInteractionJankMonitor = interactionJankMonitor;
@@ -2594,24 +2612,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| !mLockPatternUtils.isSecure(user);
// Don't trigger active unlock if fp is locked out
- final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
+ final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
// Don't trigger active unlock if primary auth is required
- final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
- final boolean isLockDown =
- containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
- || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
- final boolean isEncryptedOrTimedOut =
- containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT)
- || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
+ final boolean primaryAuthRequired = !isUnlockingWithBiometricAllowed(true);
final boolean shouldTriggerActiveUnlock =
(mAuthInterruptActive || triggerActiveUnlockForAssistant || awakeKeyguard)
&& !mSwitchingUser
&& !userCanDismissLockScreen
- && !fpLockedout
- && !isLockDown
- && !isEncryptedOrTimedOut
+ && !fpLockedOut
+ && !primaryAuthRequired
&& !mKeyguardGoingAway
&& !mSecureCameraLaunched;
@@ -2623,9 +2634,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
shouldTriggerActiveUnlock,
awakeKeyguard,
mAuthInterruptActive,
- isEncryptedOrTimedOut,
- fpLockedout,
- isLockDown,
+ fpLockedOut,
+ primaryAuthRequired,
mSwitchingUser,
triggerActiveUnlockForAssistant,
userCanDismissLockScreen));
@@ -2677,7 +2687,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
&& !fingerprintDisabledForUser
&& (!mKeyguardGoingAway || !mDeviceInteractive)
&& mIsPrimaryUser
- && biometricEnabledForUser;
+ && biometricEnabledForUser
+ && !isUserInLockdown(user);
final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
final boolean shouldListenBouncerState =
@@ -2739,14 +2750,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive
&& !statusBarShadeLocked;
final int user = getCurrentUser();
- final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
- final boolean isLockDown =
- containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
- || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
- final boolean isEncryptedOrTimedOut =
- containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT)
- || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
- final boolean fpLockedOut = isFingerprintLockedOut();
+ final boolean faceAuthAllowed = isUnlockingWithBiometricAllowed(FACE);
final boolean canBypass = mKeyguardBypassController != null
&& mKeyguardBypassController.canBypass();
// There's no reason to ask the HAL for authentication when the user can dismiss the
@@ -2754,20 +2758,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// the lock screen even when TrustAgents are keeping the device unlocked.
final boolean userNotTrustedOrDetectionIsNeeded = !getUserHasTrust(user) || canBypass;
- // Scan even when encrypted or timeout to show a preemptive bouncer when bypassing.
- // Lock-down mode shouldn't scan, since it is more explicit.
- boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass
- && !mPrimaryBouncerFullyShown);
-
- // If the device supports face detection (without authentication) and bypass is enabled,
- // allow face scanning to happen if the device is in lockdown mode.
- // Otherwise, prevent scanning.
- final boolean supportsDetectOnly = !mFaceSensorProperties.isEmpty()
- && canBypass
- && mFaceSensorProperties.get(0).supportsFaceDetection;
- if (isLockDown && !supportsDetectOnly) {
- strongAuthAllowsScanning = false;
- }
+ // If the device supports face detection (without authentication), if bypass is enabled,
+ // allow face detection to happen even if stronger auth is required. When face is detected,
+ // we show the bouncer. However, if the user manually locked down the device themselves,
+ // never attempt to detect face.
+ final boolean supportsDetect = !mFaceSensorProperties.isEmpty()
+ && mFaceSensorProperties.get(0).supportsFaceDetection
+ && canBypass && !mPrimaryBouncerIsOrWillBeShowing
+ && !isUserInLockdown(user);
+ final boolean faceAuthAllowedOrDetectionIsNeeded = faceAuthAllowed || supportsDetect;
// If the face or fp has recently been authenticated do not attempt to authenticate again.
final boolean faceAndFpNotAuthenticated = !getUserUnlockedWithBiometric(user);
@@ -2788,14 +2787,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| mUdfpsBouncerShowing)
&& !mSwitchingUser && !faceDisabledForUser && userNotTrustedOrDetectionIsNeeded
&& !mKeyguardGoingAway && biometricEnabledForUser
- && strongAuthAllowsScanning && mIsPrimaryUser
+ && faceAuthAllowedOrDetectionIsNeeded && mIsPrimaryUser
&& (!mSecureCameraLaunched || mOccludingAppRequestingFace)
&& faceAndFpNotAuthenticated
- && !mGoingToSleep
- // We only care about fp locked out state and not face because we still trigger
- // face auth even when face is locked out to show the user a message that face
- // unlock was supposed to run but didn't
- && !fpLockedOut;
+ && !mGoingToSleep;
// Aggregate relevant fields for debug logging.
maybeLogListenerModelData(
@@ -2805,19 +2800,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
shouldListen,
mAuthInterruptActive,
biometricEnabledForUser,
- mPrimaryBouncerFullyShown,
+ mPrimaryBouncerFullyShown,
faceAndFpNotAuthenticated,
+ faceAuthAllowed,
faceDisabledForUser,
isFaceLockedOut(),
- fpLockedOut,
mGoingToSleep,
awakeKeyguard,
mKeyguardGoingAway,
shouldListenForFaceAssistant,
mOccludingAppRequestingFace,
mIsPrimaryUser,
- strongAuthAllowsScanning,
mSecureCameraLaunched,
+ supportsDetect,
mSwitchingUser,
mUdfpsBouncerShowing,
isUdfpsFingerDown,
@@ -2921,9 +2916,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// This would need to be updated for multi-sensor devices
final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
&& mFaceSensorProperties.get(0).supportsFaceDetection;
- if (isEncryptedOrLockdown(userId) && supportsFaceDetection) {
+ if (!isUnlockingWithBiometricAllowed(FACE) && supportsFaceDetection) {
+ mLogger.v("startListeningForFace - detect");
mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
} else {
+ mLogger.v("startListeningForFace - authenticate");
final boolean isBypassEnabled = mKeyguardBypassController != null
&& mKeyguardBypassController.isBypassEnabled();
mFaceManager.authenticate(null /* crypto */, mFaceCancelSignal,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index bce7d92cd8fb..5bb586e489e5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -116,6 +116,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
KeyguardState.LOCKSCREEN,
0f,
TransitionState.STARTED,
+ KeyguardTransitionRepositoryImpl::class.simpleName!!,
)
)
emitTransition(
@@ -124,6 +125,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
KeyguardState.LOCKSCREEN,
1f,
TransitionState.FINISHED,
+ KeyguardTransitionRepositoryImpl::class.simpleName!!,
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
index 9b193533805e..f3d2905121bb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
@@ -41,17 +41,14 @@ constructor(
override fun start() {
listenForTransitionToAodFromLockscreen()
- listenForTransitionToLockscreenFromAod()
+ listenForTransitionToLockscreenFromDozeStates()
}
private fun listenForTransitionToAodFromLockscreen() {
scope.launch {
keyguardInteractor
.dozeTransitionTo(DozeStateModel.DOZE_AOD)
- .sample(
- keyguardTransitionInteractor.startedKeyguardTransitionStep,
- { a, b -> Pair(a, b) }
- )
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (dozeToAod, lastStartedStep) = pair
if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
@@ -68,21 +65,19 @@ constructor(
}
}
- private fun listenForTransitionToLockscreenFromAod() {
+ private fun listenForTransitionToLockscreenFromDozeStates() {
+ val canGoToLockscreen = setOf(KeyguardState.AOD, KeyguardState.DOZING)
scope.launch {
keyguardInteractor
.dozeTransitionTo(DozeStateModel.FINISH)
- .sample(
- keyguardTransitionInteractor.startedKeyguardTransitionStep,
- { a, b -> Pair(a, b) }
- )
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (dozeToAod, lastStartedStep) = pair
- if (lastStartedStep.to == KeyguardState.AOD) {
+ if (canGoToLockscreen.contains(lastStartedStep.to)) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
- KeyguardState.AOD,
+ lastStartedStep.to,
KeyguardState.LOCKSCREEN,
getAnimator(),
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
index 2a220fcd75a6..dad166f2b5e0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
@@ -21,9 +21,7 @@ import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.util.kotlin.sample
@@ -42,9 +40,6 @@ constructor(
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : TransitionInteractor(AodToGoneTransitionInteractor::class.simpleName!!) {
- private val wakeAndUnlockModes =
- setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
-
override fun start() {
scope.launch {
keyguardInteractor.biometricUnlockState
@@ -52,8 +47,7 @@ constructor(
.collect { pair ->
val (biometricUnlockState, keyguardState) = pair
if (
- keyguardState == KeyguardState.AOD &&
- wakeAndUnlockModes.contains(biometricUnlockState)
+ keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
deleted file mode 100644
index 9cbf9eac686a..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.DozeStateModel
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.launch
-
-@SysUISingleton
-class DreamingLockscreenTransitionInteractor
-@Inject
-constructor(
- @Application private val scope: CoroutineScope,
- private val keyguardInteractor: KeyguardInteractor,
- private val keyguardTransitionRepository: KeyguardTransitionRepository,
- private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(DreamingLockscreenTransitionInteractor::class.simpleName!!) {
-
- override fun start() {
- scope.launch {
- keyguardInteractor.isDreaming
- .sample(
- combine(
- keyguardInteractor.dozeTransitionModel,
- keyguardTransitionInteractor.finishedKeyguardState
- ) { a, b -> Pair(a, b) },
- { a, bc -> Triple(a, bc.first, bc.second) }
- )
- .collect { triple ->
- val (isDreaming, dozeTransitionModel, keyguardState) = triple
- // Dozing/AOD and dreaming have overlapping events. If the state remains in
- // FINISH, it means that doze mode is not running and DREAMING is ok to
- // commence.
- if (dozeTransitionModel.to == DozeStateModel.FINISH) {
- if (isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- name,
- KeyguardState.LOCKSCREEN,
- KeyguardState.DREAMING,
- getAnimator(),
- )
- )
- } else if (!isDreaming && keyguardState == KeyguardState.DREAMING) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- name,
- KeyguardState.DREAMING,
- KeyguardState.LOCKSCREEN,
- getAnimator(),
- )
- )
- }
- }
- }
- }
- }
-
- private fun getAnimator(): ValueAnimator {
- return ValueAnimator().apply {
- setInterpolator(Interpolators.LINEAR)
- setDuration(TRANSITION_DURATION_MS)
- }
- }
-
- companion object {
- private const val TRANSITION_DURATION_MS = 500L
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt
deleted file mode 100644
index e34981eabcac..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep
-import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.launch
-
-@SysUISingleton
-class DreamingToAodTransitionInteractor
-@Inject
-constructor(
- @Application private val scope: CoroutineScope,
- private val keyguardInteractor: KeyguardInteractor,
- private val keyguardTransitionRepository: KeyguardTransitionRepository,
- private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor("DREAMING->AOD") {
-
- override fun start() {
- scope.launch {
- keyguardInteractor.wakefulnessModel
- .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
- .collect { pair ->
- val (wakefulnessState, keyguardState) = pair
- if (
- isSleepingOrStartingToSleep(wakefulnessState) &&
- keyguardState == KeyguardState.DREAMING
- ) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- name,
- KeyguardState.DREAMING,
- KeyguardState.AOD,
- getAnimator(),
- )
- )
- }
- }
- }
- }
-
- private fun getAnimator(): ValueAnimator {
- return ValueAnimator().apply {
- setInterpolator(Interpolators.LINEAR)
- setDuration(TRANSITION_DURATION_MS)
- }
- }
-
- companion object {
- private const val TRANSITION_DURATION_MS = 300L
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
new file mode 100644
index 000000000000..b73ce9e5689c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class DreamingTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor(DreamingTransitionInteractor::class.simpleName!!) {
+
+ private val canDreamFrom =
+ setOf(KeyguardState.LOCKSCREEN, KeyguardState.GONE, KeyguardState.DOZING)
+
+ override fun start() {
+ listenForEntryToDreaming()
+ listenForDreamingToLockscreen()
+ listenForDreamingToGone()
+ listenForDreamingToDozing()
+ }
+
+ private fun listenForEntryToDreaming() {
+ scope.launch {
+ keyguardInteractor.isDreaming
+ .sample(
+ combine(
+ keyguardInteractor.dozeTransitionModel,
+ keyguardTransitionInteractor.finishedKeyguardState,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { triple ->
+ val (isDreaming, dozeTransitionModel, keyguardState) = triple
+ // Dozing/AOD and dreaming have overlapping events. If the state remains in
+ // FINISH, it means that doze mode is not running and DREAMING is ok to
+ // commence.
+ if (
+ isDozeOff(dozeTransitionModel.to) &&
+ isDreaming &&
+ canDreamFrom.contains(keyguardState)
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ keyguardState,
+ KeyguardState.DREAMING,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingToLockscreen() {
+ scope.launch {
+ keyguardInteractor.isDreaming
+ .sample(
+ combine(
+ keyguardInteractor.dozeTransitionModel,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ ::Pair,
+ ),
+ ::toTriple
+ )
+ .collect { triple ->
+ val (isDreaming, dozeTransitionModel, lastStartedTransition) = triple
+ if (
+ isDozeOff(dozeTransitionModel.to) &&
+ !isDreaming &&
+ lastStartedTransition.to == KeyguardState.DREAMING
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DREAMING,
+ KeyguardState.LOCKSCREEN,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingToGone() {
+ scope.launch {
+ keyguardInteractor.biometricUnlockState
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+ .collect { pair ->
+ val (biometricUnlockState, keyguardState) = pair
+ if (
+ keyguardState == KeyguardState.DREAMING &&
+ isWakeAndUnlock(biometricUnlockState)
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DREAMING,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingToDozing() {
+ scope.launch {
+ combine(
+ keyguardInteractor.dozeTransitionModel,
+ keyguardTransitionInteractor.finishedKeyguardState,
+ ::Pair
+ )
+ .collect { pair ->
+ val (dozeTransitionModel, keyguardState) = pair
+ if (
+ dozeTransitionModel.to == DozeStateModel.DOZE &&
+ keyguardState == KeyguardState.DREAMING
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DREAMING,
+ KeyguardState.DOZING,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 500L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
index 483041a1b236..a50e75909dd8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
@@ -37,7 +37,7 @@ constructor(
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor("GONE->AOD") {
+) : TransitionInteractor(GoneAodTransitionInteractor::class.simpleName!!) {
override fun start() {
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index e30e7f6ece11..a2661d76d90d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -46,6 +46,8 @@ constructor(
scope.launch { keyguardInteractor.isDozing.collect { logger.v("isDozing", it) } }
+ scope.launch { keyguardInteractor.isDreaming.collect { logger.v("isDreaming", it) } }
+
scope.launch {
interactor.finishedKeyguardTransitionStep.collect {
logger.i("Finished transition", it)
@@ -61,5 +63,9 @@ constructor(
scope.launch {
interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
}
+
+ scope.launch {
+ keyguardInteractor.dozeTransitionModel.collect { logger.i("Doze transition", it) }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index 43dd358e4808..bb8b79a3aa6b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -43,8 +43,7 @@ constructor(
is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
- is DreamingLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
- is DreamingToAodTransitionInteractor -> Log.d(TAG, "Started $it")
+ is DreamingTransitionInteractor -> Log.d(TAG, "Started $it")
}
it.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
index 4100f7a8413a..95d96025cf4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
@@ -37,15 +37,15 @@ constructor(
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
-) : TransitionInteractor("LOCKSCREEN->GONE") {
+) : TransitionInteractor(LockscreenGoneTransitionInteractor::class.simpleName!!) {
override fun start() {
scope.launch {
keyguardInteractor.isKeyguardGoingAway
- .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
- val (isKeyguardGoingAway, keyguardState) = pair
- if (!isKeyguardGoingAway && keyguardState == KeyguardState.LOCKSCREEN) {
+ val (isKeyguardGoingAway, lastStartedStep) = pair
+ if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index dbffeab436a4..5f63ae765854 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -52,13 +52,5 @@ abstract class StartKeyguardTransitionModule {
@IntoSet
abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
- @Binds
- @IntoSet
- abstract fun dreamingLockscreen(
- impl: DreamingLockscreenTransitionInteractor
- ): TransitionInteractor
-
- @Binds
- @IntoSet
- abstract fun dreamingToAod(impl: DreamingToAodTransitionInteractor): TransitionInteractor
+ @Binds @IntoSet abstract fun dreaming(impl: DreamingTransitionInteractor): TransitionInteractor
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index a2a46d9e3a71..08ad3d5bdbf6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -29,4 +29,6 @@ package com.android.systemui.keyguard.domain.interactor
sealed class TransitionInteractor(val name: String) {
abstract fun start()
+
+ fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
index db709b476c5d..8fe6309fc005 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
@@ -46,5 +46,14 @@ enum class BiometricUnlockModel {
/** When bouncer is visible and will be dismissed. */
DISMISS_BOUNCER,
/** Mode in which fingerprint wakes and unlocks the device from a dream. */
- WAKE_AND_UNLOCK_FROM_DREAM,
+ WAKE_AND_UNLOCK_FROM_DREAM;
+
+ companion object {
+ private val wakeAndUnlockModes =
+ setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
+
+ fun isWakeAndUnlock(model: BiometricUnlockModel): Boolean {
+ return wakeAndUnlockModes.contains(model)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt
index 7039188d9e96..65b7cf732f9d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt
@@ -42,5 +42,11 @@ enum class DozeStateModel {
/** AOD, prox is near, transitions to DOZE_AOD_PAUSED after a timeout. */
DOZE_AOD_PAUSING,
/** Always-on doze. Device is awake, showing docking UI and listening for pulse triggers. */
- DOZE_AOD_DOCKED
+ DOZE_AOD_DOCKED;
+
+ companion object {
+ fun isDozeOff(model: DozeStateModel): Boolean {
+ return model == UNINITIALIZED || model == FINISH
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index 120f7d673881..b55bedda2dc1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -58,6 +58,27 @@ class MediaTttLogger(
)
}
+ /**
+ * Logs an invalid sender state transition error in trying to update to [desiredState].
+ *
+ * @param currentState the previous state of the chip.
+ * @param desiredState the new state of the chip.
+ */
+ fun logInvalidStateTransitionError(
+ currentState: String,
+ desiredState: String
+ ) {
+ buffer.log(
+ tag,
+ LogLevel.ERROR,
+ {
+ str1 = currentState
+ str2 = desiredState
+ },
+ { "Cannot display state=$str2 after state=$str1; invalid transition" }
+ )
+ }
+
/** Logs that we couldn't find information for [packageName]. */
fun logPackageNotFound(packageName: String) {
buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index af7317c208ab..1f27582cb7aa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -56,7 +56,12 @@ enum class ChipStateSender(
R.string.media_move_closer_to_start_cast,
transferStatus = TransferStatus.NOT_STARTED,
endItem = null,
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+ }
+ },
/**
* A state representing that the two devices are close but not close enough to *end* a cast
@@ -70,7 +75,12 @@ enum class ChipStateSender(
R.string.media_move_closer_to_end_cast,
transferStatus = TransferStatus.NOT_STARTED,
endItem = null,
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ }
+ },
/**
* A state representing that a transfer to the receiver device has been initiated (but not
@@ -83,7 +93,13 @@ enum class ChipStateSender(
transferStatus = TransferStatus.IN_PROGRESS,
endItem = SenderEndItem.Loading,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == TRANSFER_TO_RECEIVER_SUCCEEDED ||
+ nextState == TRANSFER_TO_RECEIVER_FAILED
+ }
+ },
/**
* A state representing that a transfer from the receiver device and back to this device (the
@@ -96,7 +112,13 @@ enum class ChipStateSender(
transferStatus = TransferStatus.IN_PROGRESS,
endItem = SenderEndItem.Loading,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == TRANSFER_TO_THIS_DEVICE_SUCCEEDED ||
+ nextState == TRANSFER_TO_THIS_DEVICE_FAILED
+ }
+ },
/**
* A state representing that a transfer to the receiver device has been successfully completed.
@@ -112,7 +134,13 @@ enum class ChipStateSender(
newState =
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED
),
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == ALMOST_CLOSE_TO_START_CAST ||
+ nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ }
+ },
/**
* A state representing that a transfer back to this device has been successfully completed.
@@ -128,7 +156,13 @@ enum class ChipStateSender(
newState =
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED
),
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == ALMOST_CLOSE_TO_END_CAST ||
+ nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+ }
+ },
/** A state representing that a transfer to the receiver device has failed. */
TRANSFER_TO_RECEIVER_FAILED(
@@ -137,7 +171,13 @@ enum class ChipStateSender(
R.string.media_transfer_failed,
transferStatus = TransferStatus.FAILED,
endItem = SenderEndItem.Error,
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == ALMOST_CLOSE_TO_START_CAST ||
+ nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ }
+ },
/** A state representing that a transfer back to this device has failed. */
TRANSFER_TO_THIS_DEVICE_FAILED(
@@ -146,7 +186,13 @@ enum class ChipStateSender(
R.string.media_transfer_failed,
transferStatus = TransferStatus.FAILED,
endItem = SenderEndItem.Error,
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == ALMOST_CLOSE_TO_END_CAST ||
+ nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+ }
+ },
/** A state representing that this device is far away from any receiver device. */
FAR_FROM_RECEIVER(
@@ -162,6 +208,12 @@ enum class ChipStateSender(
throw IllegalArgumentException("FAR_FROM_RECEIVER should never be displayed, " +
"so its string should never be fetched")
}
+
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState.transferStatus == TransferStatus.NOT_STARTED ||
+ nextState.transferStatus == TransferStatus.IN_PROGRESS
+ }
};
/**
@@ -175,6 +227,8 @@ enum class ChipStateSender(
return Text.Loaded(context.getString(stringResId!!, otherDeviceName))
}
+ abstract fun isValidNextState(nextState: ChipStateSender): Boolean
+
companion object {
/**
* Returns the sender state enum associated with the given [displayState] from
@@ -197,6 +251,31 @@ enum class ChipStateSender(
*/
@StatusBarManager.MediaTransferSenderState
fun getSenderStateIdFromName(name: String): Int = valueOf(name).stateInt
+
+ /**
+ * Validates the transition from a chip state to another.
+ *
+ * @param currentState is the current state of the chip.
+ * @param desiredState is the desired state of the chip.
+ * @return true if the transition from [currentState] to [desiredState] is valid, and false
+ * otherwise.
+ */
+ fun isValidStateTransition(
+ currentState: ChipStateSender?,
+ desiredState: ChipStateSender,
+ ): Boolean {
+ // Far from receiver is the default state.
+ if (currentState == null) {
+ return FAR_FROM_RECEIVER.isValidNextState(desiredState)
+ }
+
+ // No change in state is valid.
+ if (currentState == desiredState) {
+ return true
+ }
+
+ return currentState.isValidNextState(desiredState)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index bb7bc6fff99f..e34b0cbdbdc3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -52,6 +52,8 @@ constructor(
) : CoreStartable {
private var displayedState: ChipStateSender? = null
+ // A map to store current chip state per id.
+ private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf()
private val commandQueueCallbacks =
object : CommandQueue.Callbacks {
@@ -87,9 +89,22 @@ constructor(
logger.logStateChangeError(displayState)
return
}
+
+ val currentState = stateMap[routeInfo.id]
+ if (!ChipStateSender.isValidStateTransition(currentState, chipState)) {
+ // ChipStateSender.FAR_FROM_RECEIVER is the default state when there is no state.
+ logger.logInvalidStateTransitionError(
+ currentState = currentState?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name,
+ chipState.name
+ )
+ return
+ }
uiEventLogger.logSenderStateChange(chipState)
+ stateMap.put(routeInfo.id, chipState)
if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
+ // No need to store the state since it is the default state
+ stateMap.remove(routeInfo.id)
// Return early if we're not displaying a chip anyway
val currentDisplayedState = displayedState ?: return
@@ -119,7 +134,7 @@ constructor(
context,
logger,
)
- )
+ ) { stateMap.remove(routeInfo.id) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 5670b6de3c36..e43d4c8a34eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -184,8 +184,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
int mWifiSignalIconId;
@Nullable
String mSsid;
- boolean mActivityIn;
- boolean mActivityOut;
@Nullable
String mWifiSignalContentDescription;
boolean mIsTransient;
@@ -203,8 +201,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
.append(",mConnected=").append(mConnected)
.append(",mWifiSignalIconId=").append(mWifiSignalIconId)
.append(",mSsid=").append(mSsid)
- .append(",mActivityIn=").append(mActivityIn)
- .append(",mActivityOut=").append(mActivityOut)
.append(",mWifiSignalContentDescription=").append(mWifiSignalContentDescription)
.append(",mIsTransient=").append(mIsTransient)
.append(",mNoDefaultNetwork=").append(mNoDefaultNetwork)
@@ -222,8 +218,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
CharSequence mDataContentDescription;
int mMobileSignalIconId;
int mQsTypeIcon;
- boolean mActivityIn;
- boolean mActivityOut;
boolean mNoSim;
boolean mRoaming;
boolean mMultipleSubs;
@@ -239,8 +233,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
.append(",mDataContentDescription=").append(mDataContentDescription)
.append(",mMobileSignalIconId=").append(mMobileSignalIconId)
.append(",mQsTypeIcon=").append(mQsTypeIcon)
- .append(",mActivityIn=").append(mActivityIn)
- .append(",mActivityOut=").append(mActivityOut)
.append(",mNoSim=").append(mNoSim)
.append(",mRoaming=").append(mRoaming)
.append(",mMultipleSubs=").append(mMultipleSubs)
@@ -271,8 +263,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
mWifiInfo.mEnabled = indicators.enabled;
mWifiInfo.mSsid = indicators.description;
- mWifiInfo.mActivityIn = indicators.activityIn;
- mWifiInfo.mActivityOut = indicators.activityOut;
mWifiInfo.mIsTransient = indicators.isTransient;
mWifiInfo.mStatusLabel = indicators.statusLabel;
refreshState(mWifiInfo);
@@ -293,8 +283,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
? indicators.typeContentDescriptionHtml : null;
mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon;
mCellularInfo.mQsTypeIcon = indicators.qsType;
- mCellularInfo.mActivityIn = indicators.activityIn;
- mCellularInfo.mActivityOut = indicators.activityOut;
mCellularInfo.mRoaming = indicators.roaming;
mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1;
refreshState(mCellularInfo);
@@ -424,8 +412,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
state.state = Tile.STATE_ACTIVE;
state.dualTarget = true;
state.value = cb.mEnabled;
- state.activityIn = cb.mEnabled && cb.mActivityIn;
- state.activityOut = cb.mEnabled && cb.mActivityOut;
final StringBuffer minimalContentDescription = new StringBuffer();
final StringBuffer minimalStateDescription = new StringBuffer();
final Resources r = mContext.getResources();
@@ -499,8 +485,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
boolean mobileDataEnabled = mDataController.isMobileDataSupported()
&& mDataController.isMobileDataEnabled();
state.value = mobileDataEnabled;
- state.activityIn = mobileDataEnabled && cb.mActivityIn;
- state.activityOut = mobileDataEnabled && cb.mActivityOut;
state.expandedAccessibilityClassName = Switch.class.getName();
if (cb.mAirplaneModeEnabled && cb.mQsTypeIcon != TelephonyIcons.ICON_CWF) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 5e47d6dd5b76..b511b5463cbf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -46,7 +46,6 @@ import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
-import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
@@ -73,11 +72,9 @@ import javax.inject.Named
* expansion of the headers in small screen portrait.
*
* [header] will be a [MotionLayout] if [Flags.COMBINED_QS_HEADERS] is enabled. In this case, the
- * [MotionLayout] has 2 transitions:
+ * [MotionLayout] has one transitions:
* * [HEADER_TRANSITION_ID]: [QQS_HEADER_CONSTRAINT] <-> [QS_HEADER_CONSTRAINT] for portrait
* handheld device configuration.
- * * [LARGE_SCREEN_HEADER_TRANSITION_ID]: [LARGE_SCREEN_HEADER_CONSTRAINT] (to itself) for all
- * other configurations
*/
@CentralSurfacesScope
class LargeScreenShadeHeaderController @Inject constructor(
@@ -104,8 +101,6 @@ class LargeScreenShadeHeaderController @Inject constructor(
@VisibleForTesting
internal val HEADER_TRANSITION_ID = R.id.header_transition
@VisibleForTesting
- internal val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
- @VisibleForTesting
internal val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
@VisibleForTesting
internal val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
@@ -120,10 +115,6 @@ class LargeScreenShadeHeaderController @Inject constructor(
}
}
- init {
- loadConstraints()
- }
-
private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
private lateinit var iconManager: StatusBarIconController.TintedIconManager
@@ -345,11 +336,11 @@ class LargeScreenShadeHeaderController @Inject constructor(
if (header is MotionLayout) {
// Use resources.getXml instead of passing the resource id due to bug b/205018300
header.getConstraintSet(QQS_HEADER_CONSTRAINT)
- .load(context, resources.getXml(R.xml.qqs_header))
+ .load(context, resources.getXml(R.xml.qqs_header))
header.getConstraintSet(QS_HEADER_CONSTRAINT)
- .load(context, resources.getXml(R.xml.qs_header))
+ .load(context, resources.getXml(R.xml.qs_header))
header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
- .load(context, resources.getXml(R.xml.large_screen_shade_header))
+ .load(context, resources.getXml(R.xml.large_screen_shade_header))
}
}
@@ -438,7 +429,6 @@ class LargeScreenShadeHeaderController @Inject constructor(
}
header as MotionLayout
if (largeScreenActive) {
- header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT).applyTo(header)
} else {
header.setTransition(HEADER_TRANSITION_ID)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index f52ace7138f2..6a192161fa93 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -200,7 +200,6 @@ import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
-import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -466,7 +465,11 @@ public final class NotificationPanelViewController implements Dumpable {
private boolean mQsTouchAboveFalsingThreshold;
private int mQsFalsingThreshold;
- /** Indicates drag starting height when swiping down or up on heads-up notifications */
+ /**
+ * Indicates drag starting height when swiping down or up on heads-up notifications.
+ * This usually serves as a threshold from when shade expansion should really start. Otherwise
+ * this value would be height of shade and it will be immediately expanded to some extent.
+ */
private int mHeadsUpStartHeight;
private HeadsUpTouchHelper mHeadsUpTouchHelper;
private boolean mListenForHeadsUp;
@@ -916,6 +919,7 @@ public final class NotificationPanelViewController implements Dumpable {
mQsFrameTranslateController = qsFrameTranslateController;
updateUserSwitcherFlags();
mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel;
+ mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
onFinishInflate();
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@@ -933,7 +937,6 @@ public final class NotificationPanelViewController implements Dumpable {
unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay);
}
});
- mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
dumpManager.registerDumpable(this);
}
@@ -1109,6 +1112,7 @@ public final class NotificationPanelViewController implements Dumpable {
mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
mKeyguardStatusViewController.init();
+ updateClockAppearance();
if (mKeyguardUserSwitcherController != null) {
// Try to close the switcher so that callbacks are triggered if necessary.
@@ -3412,9 +3416,12 @@ public final class NotificationPanelViewController implements Dumpable {
&& mQsExpansionAnimator == null && !mQsExpansionFromOverscroll;
boolean goingBetweenClosedShadeAndExpandedQs =
mQsExpandImmediate || collapsingShadeFromExpandedQs;
- // we don't want to update QS expansion when HUN is visible because then the whole shade is
- // initially hidden, even though it has non-zero height
- if (goingBetweenClosedShadeAndExpandedQs && !mHeadsUpManager.isTrackingHeadsUp()) {
+ // in split shade we react when HUN is visible only if shade height is over HUN start
+ // height - which means user is swiping down. Otherwise shade QS will either not show at all
+ // with HUN movement or it will blink when touching HUN initially
+ boolean qsShouldExpandWithHeadsUp = !mSplitShadeEnabled
+ || (!mHeadsUpManager.isTrackingHeadsUp() || expandedHeight > mHeadsUpStartHeight);
+ if (goingBetweenClosedShadeAndExpandedQs && qsShouldExpandWithHeadsUp) {
float qsExpansionFraction;
if (mSplitShadeEnabled) {
qsExpansionFraction = 1;
@@ -4587,55 +4594,6 @@ public final class NotificationPanelViewController implements Dumpable {
return new TouchHandler();
}
- private final PhoneStatusBarView.TouchEventHandler mStatusBarViewTouchEventHandler =
- new PhoneStatusBarView.TouchEventHandler() {
- @Override
- public void onInterceptTouchEvent(MotionEvent event) {
- mCentralSurfaces.onTouchEvent(event);
- }
-
- @Override
- public boolean handleTouchEvent(MotionEvent event) {
- mCentralSurfaces.onTouchEvent(event);
-
- // TODO(b/202981994): Move the touch debugging in this method to a central
- // location. (Right now, it's split between CentralSurfaces and here.)
-
- // If panels aren't enabled, ignore the gesture and don't pass it down to the
- // panel view.
- if (!mCommandQueue.panelsEnabled()) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- Log.v(
- TAG,
- String.format(
- "onTouchForwardedFromStatusBar: "
- + "panel disabled, ignoring touch at (%d,%d)",
- (int) event.getX(),
- (int) event.getY()
- )
- );
- }
- return false;
- }
-
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- // If the view that would receive the touch is disabled, just have status
- // bar eat the gesture.
- if (!mView.isEnabled()) {
- mShadeLog.logMotionEvent(event,
- "onTouchForwardedFromStatusBar: panel view disabled");
- return true;
- }
- if (isFullyCollapsed() && event.getY() < 1f) {
- // b/235889526 Eat events on the top edge of the phone when collapsed
- mShadeLog.logMotionEvent(event, "top edge touch ignored");
- return true;
- }
- }
- return mView.dispatchTouchEvent(event);
- }
- };
-
public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
return mNotificationStackScrollLayoutController;
}
@@ -5238,6 +5196,11 @@ public final class NotificationPanelViewController implements Dumpable {
}
/** */
+ public boolean sendTouchEventToView(MotionEvent event) {
+ return mView.dispatchTouchEvent(event);
+ }
+
+ /** */
public void requestLayoutOnView() {
mView.requestLayout();
}
@@ -5247,6 +5210,11 @@ public final class NotificationPanelViewController implements Dumpable {
ViewGroupFadeHelper.reset(mView);
}
+ /** */
+ public boolean isViewEnabled() {
+ return mView.isEnabled();
+ }
+
private void beginJankMonitoring() {
if (mInteractionJankMonitor == null) {
return;
@@ -5796,11 +5764,6 @@ public final class NotificationPanelViewController implements Dumpable {
mCurrentPanelState = state;
}
- /** Returns the handler that the status bar should forward touches to. */
- public PhoneStatusBarView.TouchEventHandler getStatusBarTouchEventHandler() {
- return mStatusBarViewTouchEventHandler;
- }
-
@VisibleForTesting
StatusBarStateController getStatusBarStateController() {
return mStatusBarStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 426d4fcbb27d..d000e6e75eed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -166,6 +166,7 @@ public class CommandQueue extends IStatusBar.Stub implements
private static final int MSG_UNREGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 67 << MSG_SHIFT;
private static final int MSG_TILE_SERVICE_REQUEST_LISTENING_STATE = 68 << MSG_SHIFT;
private static final int MSG_SHOW_REAR_DISPLAY_DIALOG = 69 << MSG_SHIFT;
+ private static final int MSG_GO_TO_FULLSCREEN_FROM_SPLIT = 70 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -480,6 +481,11 @@ public class CommandQueue extends IStatusBar.Stub implements
* @see IStatusBar#showRearDisplayDialog
*/
default void showRearDisplayDialog(int currentBaseState) {}
+
+ /**
+ * @see IStatusBar#goToFullscreenFromSplit
+ */
+ default void goToFullscreenFromSplit() {}
}
public CommandQueue(Context context) {
@@ -1302,6 +1308,11 @@ public class CommandQueue extends IStatusBar.Stub implements
.sendToTarget();
}
+ @Override
+ public void goToFullscreenFromSplit() {
+ mHandler.obtainMessage(MSG_GO_TO_FULLSCREEN_FROM_SPLIT).sendToTarget();
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -1741,6 +1752,12 @@ public class CommandQueue extends IStatusBar.Stub implements
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).showRearDisplayDialog((Integer) msg.obj);
}
+ break;
+ case MSG_GO_TO_FULLSCREEN_FROM_SPLIT:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).goToFullscreenFromSplit();
+ }
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index fe488a9b8336..97a47b5d1407 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -68,7 +68,7 @@ import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.LongRunning;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
@@ -224,12 +224,15 @@ public class NetworkControllerImpl extends BroadcastReceiver
/**
* Construct this controller object and register for updates.
+ *
+ * {@code @LongRunning} looper and bgExecutor instead {@code @Background} ones are used to
+ * address the b/246456655. This can be reverted after b/240663726 is fixed.
*/
@Inject
public NetworkControllerImpl(
Context context,
- @Background Looper bgLooper,
- @Background Executor bgExecutor,
+ @LongRunning Looper bgLooper,
+ @LongRunning Executor bgExecutor,
SubscriptionManager subscriptionManager,
CallbackHandler callbackHandler,
DeviceProvisionedController deviceProvisionedController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index e3336b269ca1..2d6d0a9cbeca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -983,7 +983,7 @@ public class NotificationStackScrollLayoutController {
}
public boolean isAddOrRemoveAnimationPending() {
- return mView.isAddOrRemoveAnimationPending();
+ return mView != null && mView.isAddOrRemoveAnimationPending();
}
public int getVisibleNotificationCount() {
@@ -1140,7 +1140,9 @@ public class NotificationStackScrollLayoutController {
}
public void setAlpha(float alpha) {
- mView.setAlpha(alpha);
+ if (mView != null) {
+ mView.setAlpha(alpha);
+ }
}
public float calculateAppearFraction(float height) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index c6f64f3e56ba..8d06fad0f418 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -28,7 +28,6 @@ import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.RemoteAnimationAdapter;
import android.view.View;
import android.view.ViewGroup;
@@ -283,7 +282,11 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
void animateCollapseQuickSettings();
- void onTouchEvent(MotionEvent event);
+ /** */
+ boolean getCommandQueuePanelsEnabled();
+
+ /** */
+ int getStatusBarWindowState();
BiometricUnlockController getBiometricUnlockController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 2d48ffcf9810..31cdb0549d45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -94,7 +94,6 @@ import android.view.Display;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowManager;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewGroup;
@@ -2031,43 +2030,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
}
- /** Called when a touch event occurred on {@link PhoneStatusBarView}. */
@Override
- public void onTouchEvent(MotionEvent event) {
- // TODO(b/202981994): Move this touch debugging to a central location. (Right now, it's
- // split between NotificationPanelViewController and here.)
- if (DEBUG_GESTURES) {
- if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
- EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH,
- event.getActionMasked(), (int) event.getX(), (int) event.getY(),
- mDisabled1, mDisabled2);
- }
-
- }
-
- if (SPEW) {
- Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1="
- + mDisabled1 + " mDisabled2=" + mDisabled2);
- } else if (CHATTY) {
- if (event.getAction() != MotionEvent.ACTION_MOVE) {
- Log.d(TAG, String.format(
- "panel: %s at (%f, %f) mDisabled1=0x%08x mDisabled2=0x%08x",
- MotionEvent.actionToString(event.getAction()),
- event.getRawX(), event.getRawY(), mDisabled1, mDisabled2));
- }
- }
-
- if (DEBUG_GESTURES) {
- mGestureRec.add(event);
- }
+ public boolean getCommandQueuePanelsEnabled() {
+ return mCommandQueue.panelsEnabled();
+ }
- if (mStatusBarWindowState == WINDOW_STATE_SHOWING) {
- final boolean upOrCancel =
- event.getAction() == MotionEvent.ACTION_UP ||
- event.getAction() == MotionEvent.ACTION_CANCEL;
- setInteracting(StatusBarManager.WINDOW_STATUS_BAR,
- !upOrCancel || mShadeController.isExpandedVisible());
- }
+ @Override
+ public int getStatusBarWindowState() {
+ return mStatusBarWindowState;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index a6c2b2c2771c..11bc490286f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -15,14 +15,20 @@
*/
package com.android.systemui.statusbar.phone
+import android.app.StatusBarManager.WINDOW_STATE_SHOWING
+import android.app.StatusBarManager.WINDOW_STATUS_BAR
import android.content.res.Configuration
import android.graphics.Point
+import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import com.android.systemui.R
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+import com.android.systemui.statusbar.phone.PhoneStatusBarView.TouchEventHandler
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.UNFOLD_STATUS_BAR
@@ -35,14 +41,18 @@ import java.util.Optional
import javax.inject.Inject
import javax.inject.Named
+private const val TAG = "PhoneStatusBarViewController"
+
/** Controller for [PhoneStatusBarView]. */
class PhoneStatusBarViewController private constructor(
view: PhoneStatusBarView,
@Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
+ private val centralSurfaces: CentralSurfaces,
+ private val shadeController: ShadeController,
+ private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
- touchEventHandler: PhoneStatusBarView.TouchEventHandler,
private val configurationController: ConfigurationController
) : ViewController<PhoneStatusBarView>(view) {
@@ -90,7 +100,7 @@ class PhoneStatusBarViewController private constructor(
}
init {
- mView.setTouchEventHandler(touchEventHandler)
+ mView.setTouchEventHandler(PhoneStatusBarViewTouchHandler())
mView.init(userChipViewModel)
}
@@ -120,6 +130,54 @@ class PhoneStatusBarViewController private constructor(
return viewUtil.touchIsWithinView(mView, x, y)
}
+ /** Called when a touch event occurred on {@link PhoneStatusBarView}. */
+ fun onTouchEvent(event: MotionEvent) {
+ if (centralSurfaces.statusBarWindowState == WINDOW_STATE_SHOWING) {
+ val upOrCancel =
+ event.action == MotionEvent.ACTION_UP ||
+ event.action == MotionEvent.ACTION_CANCEL
+ centralSurfaces.setInteracting(WINDOW_STATUS_BAR,
+ !upOrCancel || shadeController.isExpandedVisible)
+ }
+ }
+
+ inner class PhoneStatusBarViewTouchHandler : TouchEventHandler {
+ override fun onInterceptTouchEvent(event: MotionEvent) {
+ onTouchEvent(event)
+ }
+
+ override fun handleTouchEvent(event: MotionEvent): Boolean {
+ onTouchEvent(event)
+
+ // If panels aren't enabled, ignore the gesture and don't pass it down to the
+ // panel view.
+ if (!centralSurfaces.commandQueuePanelsEnabled) {
+ if (event.action == MotionEvent.ACTION_DOWN) {
+ Log.v(TAG, String.format("onTouchForwardedFromStatusBar: panel disabled, " +
+ "ignoring touch at (${event.x.toInt()},${event.y.toInt()})"))
+ }
+ return false
+ }
+
+ if (event.action == MotionEvent.ACTION_DOWN) {
+ // If the view that would receive the touch is disabled, just have status
+ // bar eat the gesture.
+ if (!centralSurfaces.notificationPanelViewController.isViewEnabled) {
+ shadeLogger.logMotionEvent(event,
+ "onTouchForwardedFromStatusBar: panel view disabled")
+ return true
+ }
+ if (centralSurfaces.notificationPanelViewController.isFullyCollapsed &&
+ event.y < 1f) {
+ // b/235889526 Eat events on the top edge of the phone when collapsed
+ shadeLogger.logMotionEvent(event, "top edge touch ignored")
+ return true
+ }
+ }
+ return centralSurfaces.notificationPanelViewController.sendTouchEventToView(event)
+ }
+ }
+
class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
override fun getViewCenter(view: View, outPoint: Point) =
when (view.id) {
@@ -157,20 +215,24 @@ class PhoneStatusBarViewController private constructor(
@Named(UNFOLD_STATUS_BAR)
private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
private val userChipViewModel: StatusBarUserChipViewModel,
+ private val centralSurfaces: CentralSurfaces,
+ private val shadeController: ShadeController,
+ private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
private val configurationController: ConfigurationController,
) {
fun create(
- view: PhoneStatusBarView,
- touchEventHandler: PhoneStatusBarView.TouchEventHandler
+ view: PhoneStatusBarView
) =
PhoneStatusBarViewController(
view,
progressProvider.getOrNull(),
+ centralSurfaces,
+ shadeController,
+ shadeLogger,
unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(),
userChipViewModel,
viewUtil,
- touchEventHandler,
configurationController
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index efec27099dcd..730ecded58e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -21,7 +21,6 @@ import android.view.View;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.dagger.qualifiers.RootView;
-import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
@@ -127,11 +126,9 @@ public interface StatusBarFragmentModule {
@StatusBarFragmentScope
static PhoneStatusBarViewController providePhoneStatusBarViewController(
PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory,
- @RootView PhoneStatusBarView phoneStatusBarView,
- NotificationPanelViewController notificationPanelViewController) {
+ @RootView PhoneStatusBarView phoneStatusBarView) {
return phoneStatusBarViewControllerFactory.create(
- phoneStatusBarView,
- notificationPanelViewController.getStatusBarTouchEventHandler());
+ phoneStatusBarView);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index a9d05d11dc00..ea4020861a09 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -105,8 +105,9 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
*
* This method handles inflating and attaching the view, then delegates to [updateView] to
* display the correct information in the view.
+ * @param onViewTimeout a runnable that runs after the view timeout.
*/
- fun displayView(newInfo: T) {
+ fun displayView(newInfo: T, onViewTimeout: Runnable? = null) {
val currentDisplayInfo = displayInfo
// Update our list of active devices by removing it if necessary, then adding back at the
@@ -173,7 +174,10 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
cancelViewTimeout?.run()
}
cancelViewTimeout = mainExecutor.executeDelayed(
- { removeView(id, REMOVAL_REASON_TIMEOUT) },
+ {
+ removeView(id, REMOVAL_REASON_TIMEOUT)
+ onViewTimeout?.run()
+ },
timeout.toLong()
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index ae30ca0e6a61..162c915552ed 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -22,6 +22,8 @@ import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
import android.hardware.display.DisplayManager
import android.hardware.input.InputManager
+import android.os.Handler
+import android.os.Looper
import android.os.Trace
import android.view.Choreographer
import android.view.Display
@@ -32,6 +34,7 @@ import android.view.SurfaceControlViewHost
import android.view.SurfaceSession
import android.view.WindowManager
import android.view.WindowlessWindowManager
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.statusbar.LightRevealEffect
@@ -40,6 +43,7 @@ import com.android.systemui.statusbar.LinearLightRevealEffect
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
+import com.android.systemui.util.Assert.isMainThread
import com.android.systemui.util.traceSection
import com.android.wm.shell.displayareahelper.DisplayAreaHelper
import java.util.Optional
@@ -59,6 +63,7 @@ constructor(
private val displayAreaHelper: Optional<DisplayAreaHelper>,
@Main private val executor: Executor,
@UiBackground private val backgroundExecutor: Executor,
+ @Background private val bgHandler: Handler,
private val rotationChangeProvider: RotationChangeProvider,
) {
@@ -120,11 +125,11 @@ constructor(
try {
// Add the view only if we are unfolding and this is the first screen on
if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) {
- addView(onOverlayReady)
+ executeInBackground { addView(onOverlayReady) }
isUnfoldHandled = true
} else {
// No unfold transition, immediately report that overlay is ready
- ensureOverlayRemoved()
+ executeInBackground { ensureOverlayRemoved() }
onOverlayReady.run()
}
} finally {
@@ -139,6 +144,7 @@ constructor(
return
}
+ ensureInBackground()
ensureOverlayRemoved()
val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
@@ -152,7 +158,7 @@ constructor(
val params = getLayoutParams()
newRoot.setView(newView, params)
- onOverlayReady?.let { callback ->
+ if (onOverlayReady != null) {
Trace.beginAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
newRoot.relayout(params) { transaction ->
@@ -170,7 +176,7 @@ constructor(
.setFrameTimelineVsync(vsyncId + 1)
.addTransactionCommittedListener(backgroundExecutor) {
Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
- callback.run()
+ onOverlayReady.run()
}
.apply()
}
@@ -213,9 +219,12 @@ constructor(
}
private fun ensureOverlayRemoved() {
- root?.release()
- root = null
- scrimView = null
+ ensureInBackground()
+ traceSection("ensureOverlayRemoved") {
+ root?.release()
+ root = null
+ scrimView = null
+ }
}
private fun getUnfoldedDisplayInfo(): DisplayInfo =
@@ -228,17 +237,17 @@ constructor(
private inner class TransitionListener : TransitionProgressListener {
override fun onTransitionProgress(progress: Float) {
- scrimView?.revealAmount = progress
+ executeInBackground { scrimView?.revealAmount = progress }
}
override fun onTransitionFinished() {
- ensureOverlayRemoved()
+ executeInBackground { ensureOverlayRemoved() }
}
override fun onTransitionStarted() {
// Add view for folding case (when unfolding the view is added earlier)
if (scrimView == null) {
- addView()
+ executeInBackground { addView() }
}
// Disable input dispatching during transition.
InputManager.getInstance().cancelCurrentTouch()
@@ -250,19 +259,35 @@ constructor(
traceSection("UnfoldLightRevealOverlayAnimation#onRotationChanged") {
if (currentRotation != newRotation) {
currentRotation = newRotation
- scrimView?.revealEffect = createLightRevealEffect()
- root?.relayout(getLayoutParams())
+ executeInBackground {
+ scrimView?.revealEffect = createLightRevealEffect()
+ root?.relayout(getLayoutParams())
+ }
}
}
}
}
+ private fun executeInBackground(f: () -> Unit) {
+ ensureInMainThread()
+ // The UiBackground executor is not used as it doesn't have a prepared looper.
+ bgHandler.post(f)
+ }
+
+ private fun ensureInBackground() {
+ check(Looper.myLooper() == bgHandler.looper) { "Not being executed in the background!" }
+ }
+
+ private fun ensureInMainThread() {
+ isMainThread()
+ }
+
private inner class FoldListener :
FoldStateListener(
context,
Consumer { isFolded ->
if (isFolded) {
- ensureOverlayRemoved()
+ executeInBackground { ensureOverlayRemoved() }
isUnfoldHandled = false
}
this.isFolded = isFolded
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 7c022eb41cd5..98d904e60603 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -431,6 +431,11 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0;
changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
+ } else if (stream == AudioManager.STREAM_VOICE_CALL) {
+ final boolean routedToBluetooth =
+ (mAudio.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)
+ & AudioManager.DEVICE_OUT_BLE_HEADSET) != 0;
+ changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
}
return changed;
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 101dd456a004..f836463c92a2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1769,6 +1769,7 @@ public class VolumeDialogImpl implements VolumeDialog,
if (ss.level == row.requestedLevel) {
row.requestedLevel = -1;
}
+ final boolean isVoiceCallStream = row.stream == AudioManager.STREAM_VOICE_CALL;
final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY;
final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
@@ -1813,8 +1814,12 @@ public class VolumeDialogImpl implements VolumeDialog,
} else if (isRingSilent || zenMuted) {
iconRes = row.iconMuteRes;
} else if (ss.routedToBluetooth) {
- iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
- : R.drawable.ic_volume_media_bt;
+ if (isVoiceCallStream) {
+ iconRes = R.drawable.ic_volume_bt_sco;
+ } else {
+ iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
+ : R.drawable.ic_volume_media_bt;
+ }
} else if (isStreamMuted(ss)) {
iconRes = ss.muted ? R.drawable.ic_volume_media_off : row.iconMuteRes;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 02738d5ae48b..8ef98f08c60d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -253,6 +253,12 @@ public final class WMShell implements
splitScreen.onFinishedWakingUp();
}
});
+ mCommandQueue.addCallback(new CommandQueue.Callbacks() {
+ @Override
+ public void goToFullscreenFromSplit() {
+ splitScreen.goToFullscreenFromSplit();
+ }
+ });
}
@VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index afd582a3b822..fa8c8982bccb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -87,17 +87,17 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel(
biometricSettingEnabledForUser = false,
bouncerFullyShown = false,
faceAndFpNotAuthenticated = false,
+ faceAuthAllowed = true,
faceDisabled = false,
faceLockedOut = false,
- fpLockedOut = false,
goingToSleep = false,
keyguardAwake = false,
keyguardGoingAway = false,
listeningForFaceAssistant = false,
occludingAppRequestingFaceAuth = false,
primaryUser = false,
- scanningAllowedByStrongAuth = false,
secureCameraLaunched = false,
+ supportsDetect = true,
switchingUser = false,
udfpsBouncerShowing = false,
udfpsFingerDown = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index ffd95f4041f9..d20be56d6c6b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -19,6 +19,7 @@ package com.android.keyguard
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
@@ -43,6 +44,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var keyguardPasswordView: KeyguardPasswordView
@Mock
+ private lateinit var passwordEntry: EditText
+ @Mock
lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock
lateinit var securityMode: KeyguardSecurityModel.SecurityMode
@@ -81,6 +84,9 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
).thenReturn(mKeyguardMessageArea)
Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
.thenReturn(mKeyguardMessageAreaController)
+ Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
+ Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry)
+ ).thenReturn(passwordEntry)
keyguardPasswordViewController = KeyguardPasswordViewController(
keyguardPasswordView,
keyguardUpdateMonitor,
@@ -103,7 +109,10 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- keyguardPasswordView.post { verify(keyguardPasswordView).requestFocus() }
+ keyguardPasswordView.post {
+ verify(keyguardPasswordView).requestFocus()
+ verify(keyguardPasswordView).showKeyboard()
+ }
}
@Test
@@ -115,6 +124,15 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
}
@Test
+ fun testHideKeyboardWhenOnPause() {
+ keyguardPasswordViewController.onPause()
+ keyguardPasswordView.post {
+ verify(keyguardPasswordView).clearFocus()
+ verify(keyguardPasswordView).hideKeyboard()
+ }
+ }
+
+ @Test
fun startAppearAnimation() {
keyguardPasswordViewController.startAppearAnimation()
verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index e9aa8ee2f4d3..0e92a2904436 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -26,6 +26,7 @@ import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
@@ -100,6 +101,8 @@ import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.annotation.NonNull;
+
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
@@ -267,21 +270,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
// IBiometricsFace@1.0 does not support detection, only authentication.
when(mFaceSensorProperties.isEmpty()).thenReturn(false);
+ when(mFaceSensorProperties.get(anyInt())).thenReturn(
+ createFaceSensorProperties(/* supportsFaceDetection = */ false));
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */));
- componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
- "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
- "vendor/version/revision" /* softwareVersion */));
-
- when(mFaceSensorProperties.get(anyInt())).thenReturn(new FaceSensorPropertiesInternal(
- 0 /* id */,
- FaceSensorProperties.STRENGTH_STRONG, 1 /* maxTemplatesAllowed */,
- componentInfo, FaceSensorProperties.TYPE_UNKNOWN,
- false /* supportsFaceDetection */, true /* supportsSelfIllumination */,
- false /* resetLockoutRequiresChallenge */));
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(List.of(
@@ -354,6 +345,28 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
when(mAuthController.areAllFingerprintAuthenticatorsRegistered()).thenReturn(true);
}
+ @NonNull
+ private FaceSensorPropertiesInternal createFaceSensorProperties(boolean supportsFaceDetection) {
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */));
+
+
+ return new FaceSensorPropertiesInternal(
+ 0 /* id */,
+ FaceSensorProperties.STRENGTH_STRONG,
+ 1 /* maxTemplatesAllowed */,
+ componentInfo,
+ FaceSensorProperties.TYPE_UNKNOWN,
+ supportsFaceDetection /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */);
+ }
+
@After
public void tearDown() {
if (mMockitoSession != null) {
@@ -611,7 +624,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() {
// GIVEN unlocking with biometric is allowed
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ strongAuthNotRequired();
// THEN unlocking with face and fp is allowed
Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -633,7 +646,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testUnlockingWithFaceAllowed_fingerprintLockout() {
// GIVEN unlocking with biometric is allowed
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ strongAuthNotRequired();
// WHEN fingerprint is locked out
fingerprintErrorLockedOut();
@@ -656,7 +669,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testUnlockingWithFpAllowed_fingerprintLockout() {
// GIVEN unlocking with biometric is allowed
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ strongAuthNotRequired();
// WHEN fingerprint is locked out
fingerprintErrorLockedOut();
@@ -710,10 +723,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
- public void skipsAuthentication_whenEncryptedKeyguard() {
- when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
- STRONG_AUTH_REQUIRED_AFTER_BOOT);
- mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
+ public void skipsAuthentication_whenStrongAuthRequired_nonBypass() {
+ lockscreenBypassIsNotAllowed();
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
@@ -723,15 +735,48 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
- public void requiresAuthentication_whenEncryptedKeyguard_andBypass() {
- testStrongAuthExceptOnBouncer(
- STRONG_AUTH_REQUIRED_AFTER_BOOT);
+ public void faceDetect_whenStrongAuthRequiredAndBypass() {
+ // GIVEN bypass is enabled, face detection is supported and strong auth is required
+ lockscreenBypassIsAllowed();
+ supportsFaceDetection();
+ strongAuthRequiredEncrypted();
+ keyguardIsVisible();
+
+ // WHEN the device wakes up
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+
+ // FACE detect is triggered, not authenticate
+ verify(mFaceManager).detectFace(any(), any(), anyInt());
+ verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+ anyBoolean());
+
+ // WHEN bouncer becomes visible
+ setKeyguardBouncerVisibility(true);
+ clearInvocations(mFaceManager);
+
+ // THEN face scanning is not run
+ mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+ verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+ anyBoolean());
+ verify(mFaceManager, never()).detectFace(any(), any(), anyInt());
}
@Test
- public void requiresAuthentication_whenTimeoutKeyguard_andBypass() {
- testStrongAuthExceptOnBouncer(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
+ public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() {
+ // GIVEN bypass is enabled, face detection is NOT supported and strong auth is required
+ lockscreenBypassIsAllowed();
+ strongAuthRequiredEncrypted();
+ keyguardIsVisible();
+
+ // WHEN the device wakes up
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+
+ // FACE detect and authenticate are NOT triggered
+ verify(mFaceManager, never()).detectFace(any(), any(), anyInt());
+ verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+ anyBoolean());
}
@Test
@@ -764,24 +809,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
assertThat(didFaceAuthRun).isFalse();
}
- private void testStrongAuthExceptOnBouncer(int strongAuth) {
- when(mKeyguardBypassController.canBypass()).thenReturn(true);
- mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
- when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
-
- mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
- mTestableLooper.processAllMessages();
- keyguardIsVisible();
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
-
- // Stop scanning when bouncer becomes visible
- setKeyguardBouncerVisibility(true);
- clearInvocations(mFaceManager);
- mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
- verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
- anyBoolean());
- }
-
@Test
public void testTriesToAuthenticate_whenAssistant() {
mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
@@ -792,10 +819,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testTriesToAuthenticate_whenTrustOnAgentKeyguard_ifBypass() {
- mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
- when(mKeyguardBypassController.canBypass()).thenReturn(true);
+ lockscreenBypassIsAllowed();
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
new ArrayList<>());
@@ -815,26 +841,17 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
- public void testIgnoresAuth_whenLockdown() {
+ public void testNoFaceAuth_whenLockDown() {
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+ userDeviceLockDown();
+
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ keyguardIsVisible();
mTestableLooper.processAllMessages();
- when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
- keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
- }
-
- @Test
- public void testTriesToAuthenticate_whenLockout() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
- mTestableLooper.processAllMessages();
- when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
-
- keyguardIsVisible();
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+ verify(mFaceManager, never()).detectFace(any(), any(), anyInt());
}
@Test
@@ -1329,7 +1346,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED);
setKeyguardBouncerVisibility(false /* isVisible */);
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
- when(mKeyguardBypassController.canBypass()).thenReturn(true);
+ lockscreenBypassIsAllowed();
keyguardIsVisible();
// WHEN status bar state reports a change to the keyguard that would normally indicate to
@@ -1513,11 +1530,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
userNotCurrentlySwitching();
// This disables face auth
- when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
- .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
mTestableLooper.processAllMessages();
-
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
}
@@ -1981,6 +1996,109 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
);
}
+ @Test
+ public void testStrongAuthChange_lockDown_stopsFpAndFaceListeningState() {
+ // GIVEN device is listening for face and fingerprint
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+ keyguardIsVisible();
+
+ verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+ verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
+ anyInt());
+
+ final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
+ final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
+ mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
+ mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel;
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN strong auth changes and device is in user lockdown
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+ userDeviceLockDown();
+ mKeyguardUpdateMonitor.notifyStrongAuthAllowedChanged(getCurrentUser());
+ mTestableLooper.processAllMessages();
+
+ // THEN face and fingerprint listening are cancelled
+ verify(faceCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
+ eq(false), eq(BiometricSourceType.FACE));
+ verify(fpCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
+ eq(false), eq(BiometricSourceType.FINGERPRINT));
+ }
+
+ @Test
+ public void testNonStrongBiometricAllowedChanged_stopsFaceListeningState() {
+ // GIVEN device is listening for face and fingerprint
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+ keyguardIsVisible();
+
+ verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+
+ final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
+ mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN non-strong biometric allowed changes
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+ mKeyguardUpdateMonitor.notifyNonStrongBiometricAllowedChanged(getCurrentUser());
+ mTestableLooper.processAllMessages();
+
+ // THEN face and fingerprint listening are cancelled
+ verify(faceCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
+ eq(false), eq(BiometricSourceType.FACE));
+ }
+
+ @Test
+ public void testShouldListenForFace_withLockedDown_returnsFalse()
+ throws RemoteException {
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ supportsFaceDetection();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ userDeviceLockDown();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ private void userDeviceLockDown() {
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+ when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId))
+ .thenReturn(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+ }
+
+ private void supportsFaceDetection() {
+ when(mFaceSensorProperties.get(anyInt()))
+ .thenReturn(createFaceSensorProperties(
+ /* supportsFaceDetection = */ true));
+ }
+
+ private void lockscreenBypassIsAllowed() {
+ mockCanBypassLockscreen(true);
+ }
+
+ private void mockCanBypassLockscreen(boolean canBypass) {
+ mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
+ when(mKeyguardBypassController.canBypass()).thenReturn(canBypass);
+ }
+
+ private void lockscreenBypassIsNotAllowed() {
+ mockCanBypassLockscreen(false);
+ }
+
private void cleanupKeyguardUpdateMonitor() {
if (mKeyguardUpdateMonitor != null) {
mKeyguardUpdateMonitor.removeCallback(mTestCallback);
@@ -2074,9 +2192,16 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
);
}
+ private void strongAuthRequiredEncrypted() {
+ when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+ }
+
private void strongAuthNotRequired() {
when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
.thenReturn(0);
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
}
private void currentUserDoesNotHaveTrust() {
@@ -2117,6 +2242,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
setKeyguardBouncerVisibility(true);
}
+ private void bouncerNotVisible() {
+ setKeyguardBouncerVisibility(false);
+ }
+
private void setKeyguardBouncerVisibility(boolean isVisible) {
mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(isVisible, isVisible);
mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 2b03722f9f31..ce9c1da422f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -22,6 +22,7 @@ import android.util.Log
import android.util.Log.TerribleFailure
import android.util.Log.TerribleFailureHandler
import android.view.Choreographer.FrameCallback
+import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators
@@ -97,6 +98,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
}
@Test
+ @FlakyTest(bugId = 260213291)
fun `starting second transition will cancel the first transition`() {
runBlocking(IMMEDIATE) {
val (animator, provider) = setupAnimator(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 4437394da943..311740e17310 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -265,6 +265,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
+ displayReceiverTriggered()
+ reset(vibratorHelper)
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -278,13 +280,15 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
.isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(uiEventLoggerFake.eventId(0))
+ // Event index 1 since initially displaying the triggered chip would also log an event.
+ assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
}
@Test
fun transferToReceiverSucceeded_nullUndoCallback_noUndo() {
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -297,6 +301,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToReceiverSucceeded_withUndoRunnable_undoVisible() {
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -313,6 +318,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
var undoCallbackCalled = false
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -325,8 +331,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
getChipbarView().getUndoButton().performClick()
- // Event index 1 since initially displaying the succeeded chip would also log an event
- assertThat(uiEventLoggerFake.eventId(1))
+ // Event index 2 since initially displaying the triggered and succeeded chip would also log
+ // events.
+ assertThat(uiEventLoggerFake.eventId(2))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id)
assertThat(undoCallbackCalled).isTrue()
assertThat(getChipbarView().getChipText())
@@ -335,6 +342,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
+ displayThisDeviceTriggered()
+ reset(vibratorHelper)
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -348,13 +357,15 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
.isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(uiEventLoggerFake.eventId(0))
+ // Event index 1 since initially displaying the triggered chip would also log an event.
+ assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id)
verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
}
@Test
fun transferToThisDeviceSucceeded_nullUndoCallback_noUndo() {
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -367,6 +378,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToThisDeviceSucceeded_withUndoRunnable_undoVisible() {
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -383,6 +395,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
var undoCallbackCalled = false
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -395,8 +408,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
getChipbarView().getUndoButton().performClick()
- // Event index 1 since initially displaying the succeeded chip would also log an event
- assertThat(uiEventLoggerFake.eventId(1))
+ // Event index 2 since initially displaying the triggered and succeeded chip would also log
+ // events.
+ assertThat(uiEventLoggerFake.eventId(2))
.isEqualTo(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
)
@@ -407,6 +421,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
+ displayReceiverTriggered()
+ reset(vibratorHelper)
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
routeInfo,
@@ -421,7 +437,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
- assertThat(uiEventLoggerFake.eventId(0))
+ // Event index 1 since initially displaying the triggered chip would also log an event.
+ assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id)
verify(vibratorHelper).vibrate(any<VibrationEffect>())
}
@@ -429,6 +446,12 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+ reset(vibratorHelper)
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
routeInfo,
null
@@ -442,7 +465,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
- assertThat(uiEventLoggerFake.eventId(0))
+ // Event index 1 since initially displaying the triggered chip would also log an event.
+ assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id)
verify(vibratorHelper).vibrate(any<VibrationEffect>())
}
@@ -517,6 +541,166 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
}
@Test
+ fun commandQueueCallback_receiverTriggeredThenAlmostStart_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_thisDeviceTriggeredThenAlmostEnd_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_receiverSucceededThenReceiverTriggered_invalidTransitionLogged() {
+ displayReceiverTriggered()
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null
+ )
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_thisDeviceSucceededThenThisDeviceTriggered_invalidTransitionLogged() {
+ displayThisDeviceTriggered()
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ null
+ )
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_almostStartThenReceiverSucceeded_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_almostEndThenThisDeviceSucceeded_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_AlmostStartThenReceiverFailed_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_almostEndThenThisDeviceFailed_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
fun receivesNewStateFromCommandQueue_isLogged() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
@@ -575,6 +759,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -598,6 +783,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -621,6 +807,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToReceiverSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -660,6 +847,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToThisDeviceSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -717,6 +905,26 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
private fun ChipStateSender.getExpectedStateText(): String? {
return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context)
}
+
+ // display receiver triggered state helper method to make sure we start from a valid state
+ // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_RECEIVER_TRIGGERED).
+ private fun displayReceiverTriggered() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null
+ )
+ }
+
+ // display this device triggered state helper method to make sure we start from a valid state
+ // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_THIS_DEVICE_TRIGGERED).
+ private fun displayThisDeviceTriggered() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+ }
}
private const val APP_NAME = "Fake app name"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index 9c36be62e46e..88651c1292c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -23,9 +23,11 @@ import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,6 +39,9 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
private lateinit var qsConstraint: ConstraintSet
private lateinit var largeScreenConstraint: ConstraintSet
+ @get:Rule
+ val expect: Expect = Expect.create()
+
@Before
fun setUp() {
qqsConstraint = ConstraintSet().apply {
@@ -344,6 +349,32 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
}
@Test
+ fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() {
+ val views = mapOf(
+ R.id.clock to "clock",
+ R.id.date to "date",
+ R.id.statusIcons to "icons",
+ R.id.privacy_container to "privacy",
+ R.id.carrier_group to "carriers",
+ R.id.batteryRemainingIcon to "battery",
+ )
+ views.forEach { (id, name) ->
+ expect.withMessage("$name changes height")
+ .that(qqsConstraint.getConstraint(id).layout.mHeight.fromConstraint())
+ .isEqualTo(qsConstraint.getConstraint(id).layout.mHeight.fromConstraint())
+ expect.withMessage("$name changes width")
+ .that(qqsConstraint.getConstraint(id).layout.mWidth.fromConstraint())
+ .isEqualTo(qsConstraint.getConstraint(id).layout.mWidth.fromConstraint())
+ }
+ }
+
+ private fun Int.fromConstraint() = when (this) {
+ -1 -> "MATCH_PARENT"
+ -2 -> "WRAP_CONTENT"
+ else -> toString()
+ }
+
+ @Test
fun testEmptyCutoutDateIconsAreConstrainedWidth() {
CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index 858d0e7b94d0..1d30ad9293a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -46,7 +46,6 @@ import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
-import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
@@ -77,6 +76,7 @@ import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@@ -212,20 +212,6 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
}
@Test
- fun testCorrectConstraints() {
- val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java)
-
- verify(qqsConstraints).load(eq(context), capture(captor))
- assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
-
- verify(qsConstraints).load(eq(context), capture(captor))
- assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header)
-
- verify(largeScreenConstraints).load(eq(context), capture(captor))
- assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
- }
-
- @Test
fun testControllersCreatedAndInitialized() {
verify(variableDateViewController).init()
@@ -287,16 +273,6 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
}
@Test
- fun testLargeScreenActive_true() {
- controller.largeScreenActive = false // Make sure there's a change
- clearInvocations(view)
-
- controller.largeScreenActive = true
-
- verify(view).setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
- }
-
- @Test
fun testLargeScreenActive_false() {
controller.largeScreenActive = true // Make sure there's a change
clearInvocations(view)
@@ -696,6 +672,25 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
verify(clock).pivotY = height.toFloat() / 2
}
+ @Test
+ fun onDensityOrFontScaleChanged_reloadConstraints() {
+ // After density or font scale change, constraints need to be reloaded to reflect new
+ // dimensions.
+ reset(qqsConstraints)
+ reset(qsConstraints)
+ reset(largeScreenConstraints)
+
+ configurationController.notifyDensityOrFontScaleChanged()
+
+ val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java)
+ verify(qqsConstraints).load(eq(context), capture(captor))
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
+ verify(qsConstraints).load(eq(context), capture(captor))
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header)
+ verify(largeScreenConstraints).load(eq(context), capture(captor))
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
+ }
+
private fun View.executeLayoutChange(
left: Int,
top: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index b6f74f0a13ba..56a840cae267 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -43,6 +43,7 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -530,6 +531,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
.setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
verify(mNotificationStackScrollLayoutController)
.setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture());
+ verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
+ reset(mKeyguardStatusViewController);
}
@After
@@ -609,7 +612,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Test
public void getVerticalSpaceForLockscreenNotifications_useLockIconBottomPadding_returnsSpaceAvailable() {
- setBottomPadding(/* stackScrollLayoutBottom= */ 100,
+ setBottomPadding(/* stackScrollLayoutBottom= */ 180,
/* lockIconPadding= */ 20,
/* indicationPadding= */ 0,
/* ambientPadding= */ 0);
@@ -620,7 +623,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Test
public void getVerticalSpaceForLockscreenNotifications_useIndicationBottomPadding_returnsSpaceAvailable() {
- setBottomPadding(/* stackScrollLayoutBottom= */ 100,
+ setBottomPadding(/* stackScrollLayoutBottom= */ 180,
/* lockIconPadding= */ 0,
/* indicationPadding= */ 30,
/* ambientPadding= */ 0);
@@ -631,7 +634,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Test
public void getVerticalSpaceForLockscreenNotifications_useAmbientBottomPadding_returnsSpaceAvailable() {
- setBottomPadding(/* stackScrollLayoutBottom= */ 100,
+ setBottomPadding(/* stackScrollLayoutBottom= */ 180,
/* lockIconPadding= */ 0,
/* indicationPadding= */ 0,
/* ambientPadding= */ 40);
@@ -802,66 +805,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
- public void handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
- when(mCommandQueue.panelsEnabled()).thenReturn(false);
-
- boolean returnVal = mNotificationPanelViewController
- .getStatusBarTouchEventHandler()
- .handleTouchEvent(
- MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
-
- assertThat(returnVal).isFalse();
- verify(mView, never()).dispatchTouchEvent(any());
- }
-
- @Test
- public void handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() {
- when(mCommandQueue.panelsEnabled()).thenReturn(true);
- when(mView.isEnabled()).thenReturn(false);
-
- boolean returnVal = mNotificationPanelViewController
- .getStatusBarTouchEventHandler()
- .handleTouchEvent(
- MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
-
- assertThat(returnVal).isTrue();
- verify(mView, never()).dispatchTouchEvent(any());
- }
-
- @Test
- public void handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
- when(mCommandQueue.panelsEnabled()).thenReturn(true);
- when(mView.isEnabled()).thenReturn(false);
- MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0);
-
- mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
-
- verify(mView).dispatchTouchEvent(event);
- }
-
- @Test
- public void handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
- when(mCommandQueue.panelsEnabled()).thenReturn(true);
- when(mView.isEnabled()).thenReturn(true);
- MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0);
-
- mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
-
- verify(mView).dispatchTouchEvent(event);
- }
-
- @Test
- public void handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() {
- when(mCommandQueue.panelsEnabled()).thenReturn(true);
- when(mView.isEnabled()).thenReturn(true);
- MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0);
-
- mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
-
- verify(mView, never()).dispatchTouchEvent(event);
- }
-
- @Test
public void testA11y_initializeNode() {
AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
@@ -1014,7 +957,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
- public void testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView() {
+ public void testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView_initClock() {
givenViewAttached();
when(mResources.getBoolean(
com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true);
@@ -1025,6 +968,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mNotificationPanelViewController.onFinishInflate();
verify(mUserSwitcherStubView, never()).inflate();
+ verify(mKeyguardStatusViewController, times(3)).displayClock(LARGE, /* animate */ true);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index 038af8ff5396..6155e3c16996 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -27,6 +27,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doAnswer
@@ -295,6 +296,7 @@ class ConfigurationControllerImplTest : SysuiTestCase() {
}
@Test
+ @Ignore("b/261408895")
fun equivalentConfigObject_listenerNotNotified() {
val config = mContext.resources.configuration
val listener = createAndAddListener()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index e2843a12c51f..14d239ab48cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -27,6 +27,8 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.shade.ShadeControllerImpl
+import com.android.systemui.shade.ShadeLogger
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.config.UnfoldTransitionConfig
@@ -41,6 +43,7 @@ import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -49,8 +52,6 @@ import java.util.Optional
@SmallTest
class PhoneStatusBarViewControllerTest : SysuiTestCase() {
- private val touchEventHandler = TestTouchEventHandler()
-
@Mock
private lateinit var notificationPanelViewController: NotificationPanelViewController
@Mock
@@ -66,6 +67,12 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var userChipViewModel: StatusBarUserChipViewModel
@Mock
+ private lateinit var centralSurfacesImpl: CentralSurfacesImpl
+ @Mock
+ private lateinit var shadeControllerImpl: ShadeControllerImpl
+ @Mock
+ private lateinit var shadeLogger: ShadeLogger
+ @Mock
private lateinit var viewUtil: ViewUtil
private lateinit var view: PhoneStatusBarView
@@ -88,18 +95,6 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
}
@Test
- fun constructor_setsTouchHandlerOnView() {
- val interceptEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 10f, 10f, 0)
- val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
-
- view.onInterceptTouchEvent(interceptEvent)
- view.onTouchEvent(event)
-
- assertThat(touchEventHandler.lastInterceptEvent).isEqualTo(interceptEvent)
- assertThat(touchEventHandler.lastEvent).isEqualTo(event)
- }
-
- @Test
fun onViewAttachedAndDrawn_moveFromCenterAnimationEnabled_moveFromCenterAnimationInitialized() {
val view = createViewMock()
val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
@@ -115,6 +110,66 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
verify(moveFromCenterAnimation).onViewsReady(any())
}
+ @Test
+ fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
+ `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false)
+ val returnVal = view.onTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+ assertThat(returnVal).isFalse()
+ verify(notificationPanelViewController, never()).sendTouchEventToView(any())
+ }
+
+ @Test
+ fun handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() {
+ `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
+ `when`(centralSurfacesImpl.notificationPanelViewController)
+ .thenReturn(notificationPanelViewController)
+ `when`(notificationPanelViewController.isViewEnabled).thenReturn(false)
+ val returnVal = view.onTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+ assertThat(returnVal).isTrue()
+ verify(notificationPanelViewController, never()).sendTouchEventToView(any())
+ }
+
+ @Test
+ fun handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
+ `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
+ `when`(centralSurfacesImpl.notificationPanelViewController)
+ .thenReturn(notificationPanelViewController)
+ `when`(notificationPanelViewController.isViewEnabled).thenReturn(false)
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+
+ view.onTouchEvent(event)
+
+ verify(notificationPanelViewController).sendTouchEventToView(event)
+ }
+
+ @Test
+ fun handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
+ `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
+ `when`(centralSurfacesImpl.notificationPanelViewController)
+ .thenReturn(notificationPanelViewController)
+ `when`(notificationPanelViewController.isViewEnabled).thenReturn(true)
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)
+
+ view.onTouchEvent(event)
+
+ verify(notificationPanelViewController).sendTouchEventToView(event)
+ }
+
+ @Test
+ fun handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() {
+ `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
+ `when`(centralSurfacesImpl.notificationPanelViewController)
+ .thenReturn(notificationPanelViewController)
+ `when`(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+
+ view.onTouchEvent(event)
+
+ verify(notificationPanelViewController, never()).sendTouchEventToView(any())
+ }
+
private fun createViewMock(): PhoneStatusBarView {
val view = spy(view)
val viewTreeObserver = mock(ViewTreeObserver::class.java)
@@ -128,9 +183,12 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
Optional.of(sysuiUnfoldComponent),
Optional.of(progressProvider),
userChipViewModel,
+ centralSurfacesImpl,
+ shadeControllerImpl,
+ shadeLogger,
viewUtil,
configurationController
- ).create(view, touchEventHandler).also {
+ ).create(view).also {
it.init()
}
}
@@ -140,17 +198,4 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
override var isHingeAngleEnabled: Boolean = false
override val halfFoldedTimeoutMillis: Int = 0
}
-
- private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler {
- var lastEvent: MotionEvent? = null
- var lastInterceptEvent: MotionEvent? = null
-
- override fun onInterceptTouchEvent(event: MotionEvent?) {
- lastInterceptEvent = event
- }
- override fun handleTouchEvent(event: MotionEvent?): Boolean {
- lastEvent = event
- return false
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 3769f52456fb..915ea1a8cd5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -170,6 +170,34 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
}
@Test
+ public void testVolumeChangeW_deviceOutFromBLEHeadset_doStateChanged() {
+ mVolumeController.setDeviceInteractive(false);
+ when(mWakefullnessLifcycle.getWakefulness()).thenReturn(
+ WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+ when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn(
+ AudioManager.DEVICE_OUT_BLE_HEADSET);
+
+ mVolumeController.onVolumeChangedW(
+ AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI);
+
+ verify(mCallback, times(1)).onStateChanged(any());
+ }
+
+ @Test
+ public void testVolumeChangeW_deviceOutFromA2DP_doStateChanged() {
+ mVolumeController.setDeviceInteractive(false);
+ when(mWakefullnessLifcycle.getWakefulness()).thenReturn(
+ WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+ when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn(
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+
+ mVolumeController.onVolumeChangedW(
+ AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI);
+
+ verify(mCallback, never()).onStateChanged(any());
+ }
+
+ @Test
public void testOnRemoteVolumeChanged_newStream_noNullPointer() {
MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
new file mode 100644
index 000000000000..3767fbe98dc1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+import android.os.Debug;
+import android.util.Log;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Convenience class for grabbing a heap dump after a test class is run.
+ *
+ * To use:
+ * - locally edit your test class to inherit from MemoryTrackingTestCase instead of SysuiTestCase
+ * - Watch the logcat with tag MEMORY to see the path to the .ahprof file
+ * - adb pull /path/to/something.ahprof
+ * - Download ahat from https://sites.google.com/corp/google.com/ahat/home
+ * - java -jar ~/Downloads/ahat-1.7.2.jar something.hprof
+ * - Watch output for next steps
+ * - Profit and fix leaks!
+ */
+public class MemoryTrackingTestCase extends SysuiTestCase {
+ private static File sFilesDir = null;
+ private static String sLatestTestClassName = null;
+
+ @Before public void grabFilesDir() {
+ if (sFilesDir == null) {
+ sFilesDir = mContext.getFilesDir();
+ }
+ sLatestTestClassName = getClass().getName();
+ }
+
+ @AfterClass
+ public static void dumpHeap() throws IOException {
+ if (sFilesDir == null) {
+ Log.e("MEMORY", "Somehow no test cases??");
+ return;
+ }
+ mockitoTearDown();
+ Log.w("MEMORY", "about to dump heap");
+ File path = new File(sFilesDir, sLatestTestClassName + ".ahprof");
+ Debug.dumpHprofData(path.getPath());
+ Log.w("MEMORY", "did it! Location: " + path);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 2e820574b435..5491379b39ac 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -290,13 +290,6 @@ public class FaceService extends SystemService {
return -1;
}
- if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
- // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
- // ever be invoked when the user is encrypted or lockdown.
- Slog.e(TAG, "detectFace invoked when user is not encrypted or lockdown");
- return -1;
- }
-
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFace");
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 1b20e6a47cb8..f9c8f064de96 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1545,7 +1545,7 @@ public final class DisplayManagerService extends SystemService {
mSyncRoot.notifyAll();
}
- sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
+ sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
Runnable work = updateDisplayStateLocked(device);
if (work != null) {
@@ -1564,7 +1564,7 @@ public final class DisplayManagerService extends SystemService {
// We don't bother invalidating the display info caches here because any changes to the
// display info will trigger a cache invalidation inside of LogicalDisplay before we hit
// this point.
- sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
scheduleTraversalLocked(false);
mPersistentDataStore.saveIfNeeded();
@@ -1593,7 +1593,7 @@ public final class DisplayManagerService extends SystemService {
mDisplayStates.delete(displayId);
mDisplayBrightnesses.delete(displayId);
DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
- sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+ sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
scheduleTraversalLocked(false);
if (mDisplayWindowPolicyControllers.contains(displayId)) {
@@ -1609,24 +1609,13 @@ public final class DisplayManagerService extends SystemService {
}
private void handleLogicalDisplaySwappedLocked(@NonNull LogicalDisplay display) {
- final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
- final Runnable work = updateDisplayStateLocked(device);
- if (work != null) {
- mHandler.post(work);
- }
- final int displayId = display.getDisplayIdLocked();
+ handleLogicalDisplayChangedLocked(display);
+ final int displayId = display.getDisplayIdLocked();
if (displayId == Display.DEFAULT_DISPLAY) {
notifyDefaultDisplayDeviceUpdated(display);
}
-
- DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
- if (dpc != null) {
- dpc.onDisplayChanged();
- }
- mPersistentDataStore.saveIfNeeded();
mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATIONS);
- handleLogicalDisplayChangedLocked(display);
}
private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) {
@@ -1638,7 +1627,7 @@ public final class DisplayManagerService extends SystemService {
final int displayId = display.getDisplayIdLocked();
final DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
- dpc.onDeviceStateTransition();
+ dpc.onDisplayChanged();
}
}
@@ -2348,9 +2337,13 @@ public final class DisplayManagerService extends SystemService {
}
}
- private void sendDisplayEventLocked(int displayId, @DisplayEvent int event) {
- Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
- mHandler.sendMessage(msg);
+ private void sendDisplayEventLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) {
+ // Only send updates outside of DisplayManagerService for enabled displays
+ if (display.isEnabledLocked()) {
+ int displayId = display.getDisplayIdLocked();
+ Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
+ mHandler.sendMessage(msg);
+ }
}
private void sendDisplayGroupEvent(int groupId, int event) {
@@ -2636,8 +2629,7 @@ public final class DisplayManagerService extends SystemService {
}
private void handleBrightnessChange(LogicalDisplay display) {
- sendDisplayEventLocked(display.getDisplayIdLocked(),
- DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED);
+ sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED);
}
private DisplayDevice getDeviceForDisplayLocked(int displayId) {
@@ -2854,12 +2846,12 @@ public final class DisplayManagerService extends SystemService {
* Returns the list of all display ids.
*/
@Override // Binder call
- public int[] getDisplayIds() {
+ public int[] getDisplayIds(boolean includeDisabled) {
final int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
synchronized (mSyncRoot) {
- return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid);
+ return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid, includeDisabled);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -3337,6 +3329,11 @@ public final class DisplayManagerService extends SystemService {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mSyncRoot) {
+ LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(
+ displayId, /* includeDisabled= */ false);
+ if (display == null || !display.isEnabledLocked()) {
+ return null;
+ }
DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
return dpc.getBrightnessInfo();
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index c131ed62c6b2..ecae8330d532 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE;
import static android.os.PowerManager.BRIGHTNESS_INVALID;
@@ -1457,7 +1458,7 @@ public class DisplayModeDirector {
SparseArray<Display.Mode[]> modes = new SparseArray<>();
SparseArray<Display.Mode> defaultModes = new SparseArray<>();
DisplayInfo info = new DisplayInfo();
- Display[] displays = dm.getDisplays();
+ Display[] displays = dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
for (Display d : displays) {
final int displayId = d.getDisplayId();
d.getDisplayInfo(info);
@@ -2332,7 +2333,8 @@ public class DisplayModeDirector {
sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this);
synchronized (mSensorObserverLock) {
- for (Display d : mDisplayManager.getDisplays()) {
+ for (Display d : mDisplayManager.getDisplays(
+ DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d));
}
}
@@ -2343,7 +2345,8 @@ public class DisplayModeDirector {
}
private void recalculateVotesLocked() {
- final Display[] displays = mDisplayManager.getDisplays();
+ final Display[] displays = mDisplayManager.getDisplays(
+ DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
for (Display d : displays) {
int displayId = d.getDisplayId();
Vote vote = null;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c426e69332c7..d7bbb109b6f7 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -491,6 +491,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private final String mSuspendBlockerIdProxNegative;
private final String mSuspendBlockerIdProxDebounce;
+ private boolean mIsEnabled;
+ private boolean mIsInTransition;
+
/**
* Creates the display power controller.
*/
@@ -512,6 +515,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
mDisplayStatsId = mUniqueDisplayId.hashCode();
+ mIsEnabled = logicalDisplay.isEnabledLocked();
+ mIsInTransition = logicalDisplay.isInTransitionLocked();
mHandler = new DisplayControllerHandler(handler.getLooper());
mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
@@ -789,13 +794,30 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
final DisplayDeviceConfig config = device.getDisplayDeviceConfig();
final IBinder token = device.getDisplayTokenLocked();
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
+ final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
mHandler.post(() -> {
+ boolean changed = false;
if (mDisplayDevice != device) {
+ changed = true;
mDisplayDevice = device;
mUniqueDisplayId = uniqueId;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
loadFromDisplayDeviceConfig(token, info);
+
+ // Since the underlying display-device changed, we really don't know the
+ // last command that was sent to change it's state. Lets assume it is unknown so
+ // that we trigger a change immediately.
+ mPowerState.resetScreenState();
+ }
+ if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
+ changed = true;
+ mIsEnabled = isEnabled;
+ mIsInTransition = isInTransition;
+ }
+
+ if (changed) {
if (DEBUG) {
Trace.beginAsyncSection("DisplayPowerController#updatePowerState", 0);
}
@@ -808,15 +830,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
/**
- * Called when the displays are preparing to transition from one device state to another.
- * This process involves turning off some displays so we need updatePowerState() to run and
- * calculate the new state.
- */
- public void onDeviceStateTransition() {
- sendUpdatePowerState();
- }
-
- /**
* Unregisters all listeners and interrupts all running threads; halting future work.
*
* This method should be called when the DisplayPowerController is no longer in use; i.e. when
@@ -1291,8 +1304,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mIgnoreProximityUntilChanged = false;
}
- if (!mLogicalDisplay.isEnabled()
- || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION
+ if (!mIsEnabled
+ || mIsInTransition
|| mScreenOffBecauseOfProximity) {
state = Display.STATE_OFF;
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 2f22d33f552a..f650b118b815 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -145,7 +145,7 @@ final class DisplayPowerState {
public void setScreenState(int state) {
if (mScreenState != state) {
if (DEBUG) {
- Slog.d(TAG, "setScreenState: state=" + state);
+ Slog.w(TAG, "setScreenState: state=" + Display.stateToString(state));
}
mScreenState = state;
@@ -339,6 +339,15 @@ final class DisplayPowerState {
if (mColorFade != null) mColorFade.dump(pw);
}
+ /**
+ * Resets the screen state to unknown. Useful when the underlying display-device changes for the
+ * LogicalDisplay and we do not know the last state that was sent to it.
+ */
+ void resetScreenState() {
+ mScreenState = Display.STATE_UNKNOWN;
+ mScreenReady = false;
+ }
+
private void scheduleScreenUpdate() {
if (!mScreenUpdatePending) {
mScreenUpdatePending = true;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index d14902eaf8f5..e6f27c1b0dd9 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -18,7 +18,6 @@ package com.android.server.display;
import static com.android.server.display.DisplayDeviceInfo.TOUCH_NONE;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Point;
@@ -68,33 +67,6 @@ import java.util.Objects;
final class LogicalDisplay {
private static final String TAG = "LogicalDisplay";
- /**
- * Phase indicating the logical display's existence is hidden from the rest of the framework.
- * This can happen if the current layout has specifically requested to keep this display
- * disabled.
- */
- static final int DISPLAY_PHASE_DISABLED = -1;
-
- /**
- * Phase indicating that the logical display is going through a layout transition.
- * When in this phase, other systems can choose to special case power-state handling of a
- * display that might be in a transition.
- */
- static final int DISPLAY_PHASE_LAYOUT_TRANSITION = 0;
-
- /**
- * The display is exposed to the rest of the system and its power state is determined by a
- * power-request from PowerManager.
- */
- static final int DISPLAY_PHASE_ENABLED = 1;
-
- @IntDef(prefix = {"DISPLAY_PHASE" }, value = {
- DISPLAY_PHASE_DISABLED,
- DISPLAY_PHASE_LAYOUT_TRANSITION,
- DISPLAY_PHASE_ENABLED
- })
- @interface DisplayPhase {}
-
// The layer stack we use when the display has been blanked to prevent any
// of its content from appearing.
private static final int BLANK_LAYER_STACK = -1;
@@ -159,14 +131,6 @@ final class LogicalDisplay {
private final Rect mTempDisplayRect = new Rect();
/**
- * Indicates the current phase of the display. Generally, phases supersede any
- * requests from PowerManager in DPC's calculation for the display state. Only when the
- * phase is ENABLED does PowerManager's request for the display take effect.
- */
- @DisplayPhase
- private int mPhase = DISPLAY_PHASE_ENABLED;
-
- /**
* The UID mappings for refresh rate override
*/
private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides;
@@ -181,12 +145,22 @@ final class LogicalDisplay {
*/
private final SparseArray<Float> mTempFrameRateOverride;
+ // Indicates the display is enabled (allowed to be ON).
+ private boolean mIsEnabled;
+
+ // Indicates the display is part of a transition from one device-state ({@link
+ // DeviceStateManager}) to another. Being a "part" of a transition means that either
+ // the {@link mIsEnabled} is changing, or the underlying mPrimiaryDisplayDevice is changing.
+ private boolean mIsInTransition;
+
public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
mDisplayId = displayId;
mLayerStack = layerStack;
mPrimaryDisplayDevice = primaryDisplayDevice;
mPendingFrameRateOverrideUids = new ArraySet<>();
mTempFrameRateOverride = new SparseArray<>();
+ mIsEnabled = true;
+ mIsInTransition = false;
}
/**
@@ -525,7 +499,7 @@ final class LogicalDisplay {
// Prevent displays that are disabled from receiving input.
// TODO(b/188914255): Remove once input can dispatch against device vs layerstack.
device.setDisplayFlagsLocked(t,
- (isEnabled() && device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE)
+ (isEnabledLocked() && device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE)
? SurfaceControl.DISPLAY_RECEIVES_INPUT
: 0);
@@ -767,32 +741,45 @@ final class LogicalDisplay {
return old;
}
- public void setPhase(@DisplayPhase int phase) {
- mPhase = phase;
+ /**
+ * @return {@code true} if the LogicalDisplay is enabled or {@code false}
+ * if disabled indicating that the display should be hidden from the rest of the apps and
+ * framework.
+ */
+ public boolean isEnabledLocked() {
+ return mIsEnabled;
+ }
+
+ /**
+ * Sets the display as enabled.
+ *
+ * @param enable True if enabled, false otherwise.
+ */
+ public void setEnabledLocked(boolean enabled) {
+ mIsEnabled = enabled;
}
/**
- * Returns the currently set phase for this LogicalDisplay. Phases are used when transitioning
- * from one device state to another. {@see LogicalDisplayMapper}.
+ * @return {@code true} if the LogicalDisplay is in a transition phase. This is used to indicate
+ * that we are getting ready to swap the underlying display-device and the display should be
+ * rendered appropriately to reduce jank.
*/
- @DisplayPhase
- public int getPhase() {
- return mPhase;
+ public boolean isInTransitionLocked() {
+ return mIsInTransition;
}
/**
- * @return {@code true} if the LogicalDisplay is enabled or {@code false}
- * if disabled indicating that the display should be hidden from the rest of the apps and
- * framework.
+ * Sets the transition phase.
+ * @param isInTransition True if it display is in transition.
*/
- public boolean isEnabled() {
- // DISPLAY_PHASE_LAYOUT_TRANSITION is still considered an 'enabled' phase.
- return mPhase == DISPLAY_PHASE_ENABLED || mPhase == DISPLAY_PHASE_LAYOUT_TRANSITION;
+ public void setIsInTransitionLocked(boolean isInTransition) {
+ mIsInTransition = isInTransition;
}
public void dumpLocked(PrintWriter pw) {
pw.println("mDisplayId=" + mDisplayId);
- pw.println("mPhase=" + mPhase);
+ pw.println("mIsEnabled=" + mIsEnabled);
+ pw.println("mIsInTransition=" + mIsInTransition);
pw.println("mLayerStack=" + mLayerStack);
pw.println("mHasContent=" + mHasContent);
pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 70c9e23c6af8..778e41820433 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -39,7 +39,6 @@ import android.view.DisplayAddress;
import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.display.LogicalDisplay.DisplayPhase;
import com.android.server.display.layout.Layout;
import java.io.PrintWriter;
@@ -167,6 +166,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
@NonNull Handler handler) {
+ this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap());
+ }
+
+ LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
+ @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
+ @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap) {
mSyncRoot = syncRoot;
mPowerManager = context.getSystemService(PowerManager.class);
mInteractive = mPowerManager.isInteractive();
@@ -181,7 +186,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
mDeviceStatesOnWhichToSleep = toSparseBooleanArray(context.getResources().getIntArray(
com.android.internal.R.array.config_deviceStatesOnWhichToSleep));
mDisplayDeviceRepo.addListener(this);
- mDeviceStateToLayoutMap = new DeviceStateToLayoutMap();
+ mDeviceStateToLayoutMap = deviceStateToLayoutMap;
}
@Override
@@ -218,10 +223,22 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
public LogicalDisplay getDisplayLocked(int displayId) {
- return mLogicalDisplays.get(displayId);
+ return getDisplayLocked(displayId, /* includeDisabled= */ true);
+ }
+
+ public LogicalDisplay getDisplayLocked(int displayId, boolean includeDisabled) {
+ LogicalDisplay display = mLogicalDisplays.get(displayId);
+ if (display == null || display.isEnabledLocked() || includeDisabled) {
+ return display;
+ }
+ return null;
}
public LogicalDisplay getDisplayLocked(DisplayDevice device) {
+ return getDisplayLocked(device, /* includeDisabled= */ true);
+ }
+
+ public LogicalDisplay getDisplayLocked(DisplayDevice device, boolean includeDisabled) {
if (device == null) {
return null;
}
@@ -229,21 +246,26 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
for (int i = 0; i < count; i++) {
final LogicalDisplay display = mLogicalDisplays.valueAt(i);
if (display.getPrimaryDisplayDeviceLocked() == device) {
- return display;
+ if (display.isEnabledLocked() || includeDisabled) {
+ return display;
+ }
+ return null;
}
}
return null;
}
- public int[] getDisplayIdsLocked(int callingUid) {
+ public int[] getDisplayIdsLocked(int callingUid, boolean includeDisabled) {
final int count = mLogicalDisplays.size();
int[] displayIds = new int[count];
int n = 0;
for (int i = 0; i < count; i++) {
LogicalDisplay display = mLogicalDisplays.valueAt(i);
- DisplayInfo info = display.getDisplayInfoLocked();
- if (info.hasAccess(callingUid)) {
- displayIds[n++] = mLogicalDisplays.keyAt(i);
+ if (display.isEnabledLocked() || includeDisabled) {
+ DisplayInfo info = display.getDisplayInfoLocked();
+ if (info.hasAccess(callingUid)) {
+ displayIds[n++] = mLogicalDisplays.keyAt(i);
+ }
}
}
if (n != count) {
@@ -364,14 +386,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
void setDeviceStateLocked(int state, boolean isOverrideActive) {
Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState
- + ", interactive=" + mInteractive);
+ + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted);
// As part of a state transition, we may need to turn off some displays temporarily so that
// the transition is smooth. Plus, on some devices, only one internal displays can be
- // on at a time. We use DISPLAY_PHASE_LAYOUT_TRANSITION to mark a display that needs to be
+ // on at a time. We use LogicalDisplay.setIsInTransition to mark a display that needs to be
// temporarily turned off.
- if (mDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) {
- resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION);
- }
+ resetLayoutLocked(mDeviceState, state, /* isStateChangeStarting= */ true);
mPendingDeviceState = state;
final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
mInteractive, mBootCompleted);
@@ -481,7 +501,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
final int count = mLogicalDisplays.size();
for (int i = 0; i < count; i++) {
final LogicalDisplay display = mLogicalDisplays.valueAt(i);
- if (display.getPhase() != LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) {
+ if (!display.isInTransitionLocked()) {
continue;
}
@@ -497,7 +517,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
private void transitionToPendingStateLocked() {
- resetLayoutLocked(mDeviceState, mPendingDeviceState, LogicalDisplay.DISPLAY_PHASE_ENABLED);
+ resetLayoutLocked(mDeviceState, mPendingDeviceState, /* isStateChangeStarting= */ false);
mDeviceState = mPendingDeviceState;
mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
applyLayoutLocked();
@@ -789,17 +809,17 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
/**
* Goes through all the displays used in the layouts for the specified {@code fromState} and
- * {@code toState} and applies the specified {@code phase}. When a new layout is requested, we
- * put the displays that will change into a transitional phase so that they can all be turned
- * OFF. Once all are confirmed OFF, then this method gets called again to reset the phase to
- * normal operation. This helps to ensure that all display-OFF requests are made before
+ * {@code toState} and un/marks them for transition. When a new layout is requested, we
+ * mark the displays that will change into a transitional phase so that they can all be turned
+ * OFF. Once all are confirmed OFF, then this method gets called again to reset transition
+ * marker. This helps to ensure that all display-OFF requests are made before
* display-ON which in turn hides any resizing-jank windows might incur when switching displays.
*
* @param fromState The state we are switching from.
* @param toState The state we are switching to.
- * @param phase The new phase to apply to the displays.
+ * @param isStateChangeStarting Indicates whether to start or end Transition phase.
*/
- private void resetLayoutLocked(int fromState, int toState, @DisplayPhase int phase) {
+ private void resetLayoutLocked(int fromState, int toState, boolean isStateChangeStarting) {
final Layout fromLayout = mDeviceStateToLayoutMap.get(fromState);
final Layout toLayout = mDeviceStateToLayoutMap.get(toState);
@@ -817,12 +837,16 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// new layout.
final DisplayAddress address = device.getDisplayDeviceInfoLocked().address;
- // Virtual displays do not have addresses.
+ // Virtual displays do not have addresses, so account for nulls.
final Layout.Display fromDisplay =
address != null ? fromLayout.getByAddress(address) : null;
final Layout.Display toDisplay =
address != null ? toLayout.getByAddress(address) : null;
+ // If the display is in one of the layouts but not the other, then the content will
+ // change, so in this case we also want to blank the displays to avoid jank.
+ final boolean displayNotInBothLayouts = (fromDisplay == null) != (toDisplay == null);
+
// If a layout doesn't mention a display-device at all, then the display-device defaults
// to enabled. This is why we treat null as "enabled" in the code below.
final boolean wasEnabled = fromDisplay == null || fromDisplay.isEnabled();
@@ -837,16 +861,23 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// 3) It's enabled, but it's mapped to a new logical display ID. To the user this
// would look like apps moving from one screen to another since task-stacks stay
// with the logical display [ID].
+ // 4) It's in one layout but not the other, so the content will change.
final boolean isTransitioning =
- (logicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION)
+ logicalDisplay.isInTransitionLocked()
|| (wasEnabled != willBeEnabled)
- || deviceHasNewLogicalDisplayId;
+ || deviceHasNewLogicalDisplayId
+ || displayNotInBothLayouts;
if (isTransitioning) {
- setDisplayPhase(logicalDisplay, phase);
- if (phase == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) {
- mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION);
+ if (isStateChangeStarting != logicalDisplay.isInTransitionLocked()) {
+ Slog.i(TAG, "Set isInTransition on display " + displayId + ": "
+ + isStateChangeStarting);
}
+ // This will either mark the display as "transitioning" if we are starting to change
+ // the device state, or remove the transitioning marker if the state change is
+ // ending.
+ logicalDisplay.setIsInTransitionLocked(isStateChangeStarting);
+ mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION);
}
}
}
@@ -891,9 +922,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
newDisplay.swapDisplaysLocked(oldDisplay);
}
- if (!displayLayout.isEnabled()) {
- setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_DISABLED);
- }
+ setEnabledLocked(newDisplay, displayLayout.isEnabled());
}
}
@@ -912,23 +941,25 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
display.updateLocked(mDisplayDeviceRepo);
mLogicalDisplays.put(displayId, display);
- setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_ENABLED);
return display;
}
- private void setDisplayPhase(LogicalDisplay display, @DisplayPhase int phase) {
+ private void setEnabledLocked(LogicalDisplay display, boolean isEnabled) {
final int displayId = display.getDisplayIdLocked();
final DisplayInfo info = display.getDisplayInfoLocked();
final boolean disallowSecondaryDisplay = mSingleDisplayDemoMode
&& (info.type != Display.TYPE_INTERNAL);
- if (phase != LogicalDisplay.DISPLAY_PHASE_DISABLED && disallowSecondaryDisplay) {
+ if (isEnabled && disallowSecondaryDisplay) {
Slog.i(TAG, "Not creating a logical display for a secondary display because single"
+ " display demo mode is enabled: " + display.getDisplayInfoLocked());
- phase = LogicalDisplay.DISPLAY_PHASE_DISABLED;
+ isEnabled = false;
}
- display.setPhase(phase);
+ if (display.isEnabledLocked() != isEnabled) {
+ Slog.i(TAG, "SetEnabled on display " + displayId + ": " + isEnabled);
+ display.setEnabledLocked(isEnabled);
+ }
}
private int assignDisplayGroupIdLocked(boolean isOwnDisplayGroup) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 3443d455ee82..15ba760dbca3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -138,6 +138,11 @@ public class PackageManagerServiceUtils {
public final static Predicate<PackageStateInternal> REMOVE_IF_NULL_PKG =
pkgSetting -> pkgSetting.getPkg() == null;
+ // This is a horrible hack to workaround b/240373119, specifically for fixing the T branch.
+ // A proper fix should be implemented in master instead.
+ public static final ThreadLocal<Boolean> DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS =
+ ThreadLocal.withInitial(() -> false);
+
/**
* Components of apps targeting Android T and above will stop receiving intents from
* external callers that do not match its declared intent filters.
@@ -1089,6 +1094,8 @@ public class PackageManagerServiceUtils {
PlatformCompat compat, ComponentResolverApi resolver,
List<ResolveInfo> resolveInfos, boolean isReceiver,
Intent intent, String resolvedType, int filterCallingUid) {
+ if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
+
final Printer logPrinter = DEBUG_INTENT_MATCHING
? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
: null;
@@ -1122,7 +1129,7 @@ public class PackageManagerServiceUtils {
throw new IllegalArgumentException("Unsupported component type");
}
- if (comp.getIntents().isEmpty()) {
+ if (comp == null || comp.getIntents().isEmpty()) {
continue;
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 352d4be6c7ce..a4c9684af418 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2864,6 +2864,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return key_consumed;
}
break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+ if (statusbar != null) {
+ statusbar.goToFullscreenFromSplit();
+ }
+ return key_consumed;
+ }
+ break;
case KeyEvent.KEYCODE_SLASH:
if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) {
toggleKeyboardShortcutsMenu(event.getDeviceId());
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 97a57e066fc7..3baaa9d44019 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -202,6 +202,9 @@ public class KeyguardServiceDelegate {
if (!mKeyguardState.enabled) {
mKeyguardService.setKeyguardEnabled(mKeyguardState.enabled);
}
+ if (mKeyguardState.dreaming) {
+ mKeyguardService.onDreamingStarted();
+ }
}
@Override
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index c758f487d4de..9957140162a0 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -179,4 +179,9 @@ public interface StatusBarManagerInternal {
* @see com.android.internal.statusbar.IStatusBar#showRearDisplayDialog
*/
void showRearDisplayDialog(int currentBaseState);
+
+ /**
+ * Called when requested to go to fullscreen from the active split app.
+ */
+ void goToFullscreenFromSplit();
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 194dfb25583f..5a91dc6ef8d9 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -696,6 +696,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
} catch (RemoteException ex) { }
}
}
+
+ @Override
+ public void goToFullscreenFromSplit() {
+ if (mBar != null) {
+ try {
+ mBar.goToFullscreenFromSplit();
+ } catch (RemoteException ex) { }
+ }
+ }
};
private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 6eaeb15e30b5..565b8f8d117c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -146,6 +146,7 @@ import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.HostingRecord;
import com.android.server.am.UserState;
+import com.android.server.pm.PackageManagerServiceUtils;
import com.android.server.utils.Slogf;
import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
@@ -2634,12 +2635,17 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// ActivityStarter will acquire the lock where the places need, so execute the request
// outside of the lock.
try {
+ // We need to temporarily disable the explicit intent filter matching enforcement
+ // because Task does not store the resolved type of the intent data, causing filter
+ // mismatch in certain cases. (b/240373119)
+ PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(true);
return mService.getActivityStartController().startActivityInPackage(taskCallingUid,
callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null,
null, 0, 0, options, userId, task, "startActivityFromRecents",
false /* validateIncomingUser */, null /* originatingPendingIntent */,
false /* allowBackgroundActivityStart */);
} finally {
+ PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(false);
synchronized (mService.mGlobalLock) {
mService.continueWindowLayout();
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 2c289c99d3da..0bb4022d9289 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -63,7 +63,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_WAKE;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED;
import static android.view.WindowManagerPolicyConstants.ALT_BAR_BOTTOM;
@@ -804,13 +803,7 @@ public class DisplayPolicy {
if (!mDisplayContent.isDefaultDisplay) {
return;
}
- if (mAwake && mDisplayContent.mTransitionController.isShellTransitionsEnabled()
- && !mDisplayContent.mTransitionController.isCollecting()) {
- // Start a transition for waking. This is needed for showWhenLocked activities.
- mDisplayContent.mTransitionController.requestTransitionIfNeeded(TRANSIT_WAKE,
- 0 /* flags */, null /* trigger */, mDisplayContent);
- }
- mService.mAtmService.mKeyguardController.updateDeferWakeTransition(
+ mService.mAtmService.mKeyguardController.updateDeferTransitionForAod(
mAwake /* waiting */);
}
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index d76f6be93aeb..e6a0f4d5d77d 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -176,7 +176,7 @@ class KeyguardController {
final boolean keyguardChanged = (keyguardShowing != state.mKeyguardShowing)
|| (state.mKeyguardGoingAway && keyguardShowing && !aodRemoved);
if (aodRemoved) {
- updateDeferWakeTransition(false /* waiting */);
+ updateDeferTransitionForAod(false /* waiting */);
}
if (!keyguardChanged && !aodChanged) {
setWakeTransitionReady();
@@ -535,24 +535,25 @@ class KeyguardController {
private final Runnable mResetWaitTransition = () -> {
synchronized (mWindowManager.mGlobalLock) {
- updateDeferWakeTransition(false /* waiting */);
+ updateDeferTransitionForAod(false /* waiting */);
}
};
- void updateDeferWakeTransition(boolean waiting) {
+ // Defer transition until AOD dismissed.
+ void updateDeferTransitionForAod(boolean waiting) {
if (waiting == mWaitingForWakeTransition) {
return;
}
- if (!mWindowManager.mAtmService.getTransitionController().isShellTransitionsEnabled()) {
+ if (!mService.getTransitionController().isCollecting()) {
return;
}
- // if aod is showing, defer the wake transition until aod state changed.
+ // if AOD is showing, defer the wake transition until AOD state changed.
if (waiting && isAodShowing(DEFAULT_DISPLAY)) {
mWaitingForWakeTransition = true;
mWindowManager.mAtmService.getTransitionController().deferTransitionReady();
mWindowManager.mH.postDelayed(mResetWaitTransition, DEFER_WAKE_TRANSITION_TIMEOUT_MS);
} else if (!waiting) {
- // dismiss the deferring if the aod state change or cancel awake.
+ // dismiss the deferring if the AOD state change or cancel awake.
mWaitingForWakeTransition = false;
mWindowManager.mAtmService.getTransitionController().continueTransitionReady();
mWindowManager.mH.removeCallbacks(mResetWaitTransition);
@@ -659,10 +660,18 @@ class KeyguardController {
mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
}
+ boolean hasChange = false;
if (lastOccluded != mOccluded) {
controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
+ hasChange = true;
} else if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
controller.handleKeyguardGoingAwayChanged(display);
+ hasChange = true;
+ }
+ // Collect the participates for shell transition, so that transition won't happen too
+ // early since the transition was set ready.
+ if (hasChange && top != null && (mOccluded || mKeyguardGoingAway)) {
+ display.mTransitionController.collect(top);
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 6edb63c14c64..76ee733cbb27 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -674,6 +674,12 @@ final class LetterboxUiController {
+ getHorizontalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
pw.println(prefix + " letterboxVerticalPositionMultiplier="
+ getVerticalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
+ pw.println(prefix + " letterboxPositionForHorizontalReachability="
+ + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
+ mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+ pw.println(prefix + " letterboxPositionForVerticalReachability="
+ + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
+ mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
pw.println(prefix + " fixedOrientationLetterboxAspectRatio="
+ mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
pw.println(prefix + " defaultMinAspectRatioForUnresizableApps="
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index c6989ef906d5..d619547dbbd1 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -691,6 +691,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
if (mainWindow == null || mainWindow.mRemoved) {
removalInfo.playRevealAnimation = false;
} else if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
+ removalInfo.roundedCornerRadius =
+ topActivity.mLetterboxUiController.getRoundedCornersRadius(mainWindow);
removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
removalInfo.mainFrame = mainWindow.getRelativeFrame();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index c22091b4eacb..b8cf0ad2e774 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1206,6 +1206,12 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println("Default position for vertical reachability: "
+ LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
mLetterboxConfiguration.getDefaultPositionForVerticalReachability()));
+ pw.println("Current position for horizontal reachability:"
+ + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
+ mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+ pw.println("Current position for vertical reachability:"
+ + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
+ mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
pw.println("Is education enabled: "
+ mLetterboxConfiguration.getIsEducationEnabled());
pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 1e97c1c5c5bc..2edb909258f9 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -287,7 +287,7 @@ public class DisplayManagerServiceTest {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
- final int displayIds[] = bs.getDisplayIds();
+ final int[] displayIds = bs.getDisplayIds(/* includeDisabled= */ true);
final int size = displayIds.length;
assertTrue(size > 0);
@@ -1174,7 +1174,8 @@ public class DisplayManagerServiceTest {
DisplayManagerService.BinderService displayManagerBinderService,
FakeDisplayDevice displayDevice) {
- final int[] displayIds = displayManagerBinderService.getDisplayIds();
+ final int[] displayIds = displayManagerBinderService.getDisplayIds(
+ /* includeDisabled= */ true);
assertTrue(displayIds.length > 0);
int displayId = Display.INVALID_DISPLAY;
for (int i = 0; i < displayIds.length; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index cc68ba88f76e..d515fae4afe2 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -30,6 +30,8 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -53,6 +55,8 @@ import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.display.layout.Layout;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -60,6 +64,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
import java.io.InputStream;
import java.io.OutputStream;
@@ -85,6 +90,7 @@ public class LogicalDisplayMapperTest {
@Mock Resources mResourcesMock;
@Mock IPowerManager mIPowerManagerMock;
@Mock IThermalService mIThermalServiceMock;
+ @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy = new DeviceStateToLayoutMap();
@Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
@@ -134,7 +140,8 @@ public class LogicalDisplayMapperTest {
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
- mListenerMock, new DisplayManagerService.SyncRoot(), mHandler);
+ mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
+ mDeviceStateToLayoutMapSpy);
}
@@ -261,7 +268,8 @@ public class LogicalDisplayMapperTest {
add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0));
add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
- int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID);
+ int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID,
+ /* includeDisabled= */ true);
assertEquals(3, ids.length);
Arrays.sort(ids);
assertEquals(DEFAULT_DISPLAY, ids[0]);
@@ -413,6 +421,178 @@ public class LogicalDisplayMapperTest {
/* isBootCompleted= */true));
}
+ @Test
+ public void testDeviceStateLocked() {
+ DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+ Layout layout = new Layout();
+ layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, true, true);
+ layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, false, false);
+ when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
+
+ layout = new Layout();
+ layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, false, false);
+ layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, true, true);
+ when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout);
+ when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout);
+
+ LogicalDisplay display1 = add(device1);
+ assertEquals(info(display1).address, info(device1).address);
+ assertEquals(DEFAULT_DISPLAY, id(display1));
+
+ LogicalDisplay display2 = add(device2);
+ assertEquals(info(display2).address, info(device2).address);
+ // We can only have one default display
+ assertEquals(DEFAULT_DISPLAY, id(display1));
+
+ mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+ mLooper.moveTimeForward(1000);
+ mLooper.dispatchAll();
+ assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+ assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+ assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
+ assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+
+ mLogicalDisplayMapper.setDeviceStateLocked(1, false);
+ mLooper.moveTimeForward(1000);
+ mLooper.dispatchAll();
+ assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+ assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+ assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
+ assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+
+ mLogicalDisplayMapper.setDeviceStateLocked(2, false);
+ mLooper.moveTimeForward(1000);
+ mLooper.dispatchAll();
+ assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+ assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+ assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
+ assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+ }
+
+ @Test
+ public void testEnabledAndDisabledDisplays() {
+ DisplayAddress displayAddressOne = new TestUtils.TestDisplayAddress();
+ DisplayAddress displayAddressTwo = new TestUtils.TestDisplayAddress();
+ DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress();
+
+ TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, Display.TYPE_INTERNAL,
+ 600, 800,
+ DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, Display.TYPE_INTERNAL,
+ 200, 800,
+ DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+ TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, Display.TYPE_INTERNAL,
+ 600, 900, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+
+ Layout threeDevicesEnabledLayout = new Layout();
+ threeDevicesEnabledLayout.createDisplayLocked(
+ displayAddressOne,
+ /* isDefault= */ true,
+ /* isEnabled= */ true);
+ threeDevicesEnabledLayout.createDisplayLocked(
+ displayAddressTwo,
+ /* isDefault= */ false,
+ /* isEnabled= */ true);
+ threeDevicesEnabledLayout.createDisplayLocked(
+ displayAddressThree,
+ /* isDefault= */ false,
+ /* isEnabled= */ true);
+
+ when(mDeviceStateToLayoutMapSpy.get(DeviceStateToLayoutMap.STATE_DEFAULT))
+ .thenReturn(threeDevicesEnabledLayout);
+
+ LogicalDisplay display1 = add(device1);
+ LogicalDisplay display2 = add(device2);
+ LogicalDisplay display3 = add(device3);
+
+ // ensure 3 displays are returned
+ int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID, false);
+ assertEquals(3, ids.length);
+ Arrays.sort(ids);
+ assertEquals(DEFAULT_DISPLAY, ids[0]);
+ assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1,
+ /* includeDisabled= */ false));
+ assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device2,
+ /* includeDisabled= */ false));
+ assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device3,
+ /* includeDisabled= */ false));
+ assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+ threeDevicesEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(),
+ /* includeDisabled= */ false));
+ assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+ threeDevicesEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(),
+ /* includeDisabled= */ false));
+ assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+ threeDevicesEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(),
+ /* includeDisabled= */ false));
+
+ Layout oneDeviceEnabledLayout = new Layout();
+ oneDeviceEnabledLayout.createDisplayLocked(
+ displayAddressOne,
+ /* isDefault= */ true,
+ /* isEnabled= */ true);
+ oneDeviceEnabledLayout.createDisplayLocked(
+ displayAddressTwo,
+ /* isDefault= */ false,
+ /* isEnabled= */ false);
+ oneDeviceEnabledLayout.createDisplayLocked(
+ displayAddressThree,
+ /* isDefault= */ false,
+ /* isEnabled= */ false);
+
+ when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout);
+ when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);
+
+ // 1) Set the new state
+ // 2) Mark the displays as STATE_OFF so that it can continue with transition
+ // 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state
+ // 4) Dispatch handler events.
+ mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+ mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
+ mLooper.moveTimeForward(1000);
+ mLooper.dispatchAll();
+ final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked(
+ Process.SYSTEM_UID, false);
+ if (allDisplayIds.length != 1) {
+ throw new RuntimeException("Displays: \n"
+ + mLogicalDisplayMapper.getDisplayLocked(device1).toString()
+ + "\n" + mLogicalDisplayMapper.getDisplayLocked(device2).toString()
+ + "\n" + mLogicalDisplayMapper.getDisplayLocked(device3).toString());
+ }
+ // ensure only one display is returned
+ assertEquals(1, allDisplayIds.length);
+ assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1,
+ /* includeDisabled= */ false));
+ assertNull(mLogicalDisplayMapper.getDisplayLocked(device2,
+ /* includeDisabled= */ false));
+ assertNull(mLogicalDisplayMapper.getDisplayLocked(device3,
+ /* includeDisabled= */ false));
+ assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+ oneDeviceEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(),
+ /* includeDisabled= */ false));
+ assertNull(mLogicalDisplayMapper.getDisplayLocked(
+ oneDeviceEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(),
+ /* includeDisabled= */ false));
+ assertNull(mLogicalDisplayMapper.getDisplayLocked(
+ oneDeviceEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(),
+ /* includeDisabled= */ false));
+
+ // Now do it again to go back to state 1
+ mLogicalDisplayMapper.setDeviceStateLocked(1, false);
+ mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
+ mLooper.moveTimeForward(1000);
+ mLooper.dispatchAll();
+ final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked(
+ Process.SYSTEM_UID, false);
+
+ // ensure all three displays are returned
+ assertEquals(3, threeDisplaysEnabled.length);
+ }
+
/////////////////
// Helper Methods
/////////////////
@@ -477,6 +657,7 @@ public class LogicalDisplayMapperTest {
class TestDisplayDevice extends DisplayDevice {
private DisplayDeviceInfo mInfo;
private DisplayDeviceInfo mSentInfo;
+ private int mState;
TestDisplayDevice() {
super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock);
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index b0738fdb78d0..50d2a51b4bbe 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -128,12 +128,12 @@ public class LogicalDisplayTest {
verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
reset(t);
- mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_DISABLED);
+ mLogicalDisplay.setEnabledLocked(false);
mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
verify(t).setDisplayFlags(any(), eq(0));
reset(t);
- mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_ENABLED);
+ mLogicalDisplay.setEnabledLocked(true);
mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index ce322f7cb6e6..8bd6fcdbb9c2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -38,6 +38,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.UserHandle;
@@ -47,7 +48,6 @@ import android.permission.PermissionManager;
import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
-import com.android.activitycontext.ActivityContext;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
import com.android.server.LocalServices;
@@ -611,34 +611,23 @@ public class CrossProfileAppsServiceImplTest {
mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
Bundle options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
- IBinder result = ActivityContext.getWithContext(activity -> {
- try {
- IBinder targetTask = activity.getActivityToken();
- mCrossProfileAppsServiceImpl.startActivityAsUser(
- mIApplicationThread,
- PACKAGE_ONE,
- FEATURE_ID,
- ACTIVITY_COMPONENT,
- UserHandle.of(PRIMARY_USER).getIdentifier(),
- true,
- targetTask,
- options);
- return targetTask;
- } catch (Exception re) {
- return null;
- }
- });
- if (result == null) {
- throw new Exception();
- }
-
+ Binder targetTask = new Binder();
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PRIMARY_USER).getIdentifier(),
+ true,
+ targetTask,
+ options);
verify(mActivityTaskManagerInternal)
.startActivityAsUser(
nullable(IApplicationThread.class),
eq(PACKAGE_ONE),
eq(FEATURE_ID),
any(Intent.class),
- eq(result),
+ eq(targetTask),
anyInt(),
eq(options),
eq(PRIMARY_USER));