summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java2
-rw-r--r--core/java/android/view/InsetsController.java6
-rw-r--r--core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java10
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java38
-rw-r--r--core/java/android/widget/ListView.java2
-rw-r--r--core/java/android/window/DesktopModeFlags.java2
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl8
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt46
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/RemoteTransitionHandlerTest.kt115
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt52
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionLog.kt22
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionRestrictionsHelper.kt78
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java2
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/OWNERS1
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionIntentProviderTest.kt7
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionRestrictionsHelperTest.kt178
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java12
-rw-r--r--packages/SystemUI/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt23
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt31
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryImplTest.kt120
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt109
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorTest.kt49
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorTest.kt173
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt82
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt165
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/CommunalSuppressionStartable.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalFeature.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/model/SuppressionReason.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CarProjectionRepository.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractor.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractor.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/suppression/FlowExt.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/suppression/dagger/CommunalSuppressionModule.kt103
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUiState.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/OWNERS2
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/repository/SettingsForUserRepository.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt386
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCarProjectionRepository.kt37
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorKosmos.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt39
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java66
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.kt61
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt17
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt6
-rw-r--r--services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java18
-rw-r--r--services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java6
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java20
-rw-r--r--services/core/java/com/android/server/inputmethod/ZeroJankProxy.java8
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java4
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java32
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java3
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java6
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java3
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java3
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java3
100 files changed, 2149 insertions, 1020 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 73de1b67dc66..c74bd1a092ee 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6981,6 +6981,8 @@ public class DevicePolicyManager {
* <p>The caller must hold the
* {@link android.Manifest.permission#TRIGGER_LOST_MODE} permission.
*
+ * <p>This API accesses the device's location and will only be used when a device is lost.
+ *
* <p>Register a broadcast receiver to receive lost mode location updates. This receiver should
* subscribe to the {@link #ACTION_LOST_MODE_LOCATION_UPDATE} action and receive the location
* from an intent extra {@link #EXTRA_LOST_MODE_LOCATION}.
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 6f346bdae70a..394ac8f8c6e9 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1986,7 +1986,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
// report its requested visibility at the end of the animation, otherwise we would
// lose the leash, and it would disappear during the animation
// TODO(b/326377046) revisit this part and see if we can make it more general
- typesToReport = mRequestedVisibleTypes | (mAnimatingTypes & ime());
+ if (Flags.reportAnimatingInsetsTypes()) {
+ typesToReport = mRequestedVisibleTypes;
+ } else {
+ typesToReport = mRequestedVisibleTypes | (mAnimatingTypes & ime());
+ }
} else {
typesToReport = mRequestedVisibleTypes;
}
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index eca798d6eb4f..290885593ee6 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -379,7 +379,7 @@ final class IInputMethodManagerGlobalInvoker {
@Nullable IRemoteInputConnection remoteInputConnection,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible) {
final IInputMethodManager service = getService();
if (service == null) {
return InputBindResult.NULL;
@@ -388,7 +388,7 @@ final class IInputMethodManagerGlobalInvoker {
return service.startInputOrWindowGainedFocus(startInputReason, client, windowToken,
startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection,
remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId,
- imeDispatcher);
+ imeDispatcher, imeRequestedVisible);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -408,7 +408,8 @@ final class IInputMethodManagerGlobalInvoker {
@Nullable IRemoteInputConnection remoteInputConnection,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean useAsyncShowHideMethod) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible,
+ boolean useAsyncShowHideMethod) {
final IInputMethodManager service = getService();
if (service == null) {
return -1;
@@ -417,7 +418,8 @@ final class IInputMethodManagerGlobalInvoker {
service.startInputOrWindowGainedFocusAsync(startInputReason, client, windowToken,
startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection,
remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId,
- imeDispatcher, advanceAngGetStartInputSequenceNumber(), useAsyncShowHideMethod);
+ imeDispatcher, imeRequestedVisible, advanceAngGetStartInputSequenceNumber(),
+ useAsyncShowHideMethod);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0b34600f4104..a41ab368aed8 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -871,6 +871,19 @@ public final class InputMethodManager {
IInputMethodManagerGlobalInvoker.reportPerceptibleAsync(windowToken, perceptible);
}
+ private static boolean hasViewImeRequestedVisible(View view) {
+ // before the refactor, the requestedVisibleTypes for the IME were not in sync with
+ // the state that was actually requested.
+ if (Flags.refactorInsetsController() && view != null) {
+ final var controller = view.getWindowInsetsController();
+ if (controller != null) {
+ return (view.getWindowInsetsController()
+ .getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0;
+ }
+ }
+ return false;
+ }
+
private final class DelegateImpl implements
ImeFocusController.InputMethodManagerDelegate {
@@ -941,6 +954,9 @@ public final class InputMethodManager {
Log.v(TAG, "Reporting focus gain, without startInput");
}
+ final boolean imeRequestedVisible = hasViewImeRequestedVisible(
+ mCurRootView.getView());
+
// ignore the result
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMM.startInputOrWindowGainedFocus");
IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
@@ -950,7 +966,7 @@ public final class InputMethodManager {
null,
null, null,
mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
- UserHandle.myUserId(), mImeDispatcher);
+ UserHandle.myUserId(), mImeDispatcher, imeRequestedVisible);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -2441,9 +2457,8 @@ public final class InputMethodManager {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_NO_ONGOING_USER_ANIMATION);
if (resultReceiver != null) {
- final boolean imeReqVisible =
- (viewRootImpl.getInsetsController().getRequestedVisibleTypes()
- & WindowInsets.Type.ime()) != 0;
+ final boolean imeReqVisible = hasViewImeRequestedVisible(
+ viewRootImpl.getView());
resultReceiver.send(
imeReqVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
: InputMethodManager.RESULT_SHOWN, null);
@@ -2656,9 +2671,8 @@ public final class InputMethodManager {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE);
- final boolean imeReqVisible =
- (viewRootImpl.getInsetsController().getRequestedVisibleTypes()
- & WindowInsets.Type.ime()) != 0;
+ final boolean imeReqVisible = hasViewImeRequestedVisible(
+ viewRootImpl.getView());
if (resultReceiver != null) {
resultReceiver.send(
!imeReqVisible ? InputMethodManager.RESULT_UNCHANGED_HIDDEN
@@ -3412,6 +3426,7 @@ public final class InputMethodManager {
final Handler icHandler;
InputBindResult res = null;
final boolean hasServedView;
+ final boolean imeRequestedVisible;
synchronized (mH) {
// Now that we are locked again, validate that our state hasn't
// changed.
@@ -3479,10 +3494,13 @@ public final class InputMethodManager {
}
mServedInputConnection = servedInputConnection;
+ imeRequestedVisible = hasViewImeRequestedVisible(servedView);
+
if (DEBUG) {
Log.v(TAG, "START INPUT: view=" + InputMethodDebug.dumpViewInfo(view)
+ " ic=" + ic + " editorInfo=" + editorInfo + " startInputFlags="
- + InputMethodDebug.startInputFlagsToString(startInputFlags));
+ + InputMethodDebug.startInputFlagsToString(startInputFlags)
+ + " imeRequestedVisible=" + imeRequestedVisible);
}
// When we switch between non-editable views, do not call into the IMMS.
@@ -3513,7 +3531,7 @@ public final class InputMethodManager {
servedInputConnection == null ? null
: servedInputConnection.asIRemoteAccessibilityInputConnection(),
view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
- mImeDispatcher, mAsyncShowHideMethodEnabled);
+ mImeDispatcher, imeRequestedVisible, mAsyncShowHideMethodEnabled);
} else {
res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, startInputFlags,
@@ -3521,7 +3539,7 @@ public final class InputMethodManager {
servedInputConnection == null ? null
: servedInputConnection.asIRemoteAccessibilityInputConnection(),
view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
- mImeDispatcher);
+ mImeDispatcher, imeRequestedVisible);
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (Flags.useZeroJankProxy()) {
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 3f611c7efbdd..a328c78f3738 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -72,7 +72,7 @@ import java.util.function.Predicate;
/**
* <p>Displays a vertically-scrollable collection of views, where each view is positioned
- * immediatelybelow the previous view in the list. For a more modern, flexible, and performant
+ * immediately below the previous view in the list. For a more modern, flexible, and performant
* approach to displaying lists, use {@link androidx.recyclerview.widget.RecyclerView}.</p>
*
* <p>To display a list, you can include a list view in your layout XML file:</p>
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 17165cdcf7b1..aecf6eb261b1 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -113,7 +113,7 @@ public enum DesktopModeFlags {
ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true),
ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS(Flags::enableModalsFullscreenWithPermission, false),
ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS(
- Flags::enableOpaqueBackgroundForTransparentWindows, false),
+ Flags::enableOpaqueBackgroundForTransparentWindows, true),
ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX(Flags::enableQuickswitchDesktopSplitBugfix, true),
ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true),
ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE(
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 9380d99b7de3..0791612fa0e8 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -105,7 +105,7 @@ interface IInputMethodManager {
in @nullable EditorInfo editorInfo, in @nullable IRemoteInputConnection inputConnection,
in @nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, int userId,
- in ImeOnBackInvokedDispatcher imeDispatcher);
+ in ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible);
// If windowToken is null, this just does startInput(). Otherwise this reports that a window
// has gained focus, and if 'editorInfo' is non-null then also does startInput.
@@ -120,8 +120,8 @@ interface IInputMethodManager {
in @nullable EditorInfo editorInfo, in @nullable IRemoteInputConnection inputConnection,
in @nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, int userId,
- in ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
- boolean useAsyncShowHideMethod);
+ in ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible,
+ int startInputSeq, boolean useAsyncShowHideMethod);
void showInputMethodPickerFromClient(in IInputMethodClient client,
int auxiliarySubtypeMode);
@@ -224,7 +224,7 @@ interface IInputMethodManager {
* async **/
oneway void acceptStylusHandwritingDelegationAsync(in IInputMethodClient client, in int userId,
in String delegatePackageName, in String delegatorPackageName, int flags,
- in IBooleanListener callback);
+ in IBooleanListener callback);
/** Returns {@code true} if currently selected IME supports Stylus handwriting. */
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
index 2dbbeaebd3c0..651e776891db 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
@@ -73,7 +73,11 @@ class DropTargetManager(
val newDragZone = state.getMatchingDragZone(x = x, y = y)
state.currentDragZone = newDragZone
if (oldDragZone != newDragZone) {
- dragZoneChangedListener.onDragZoneChanged(from = oldDragZone, to = newDragZone)
+ dragZoneChangedListener.onDragZoneChanged(
+ draggedObject = state.draggedObject,
+ from = oldDragZone,
+ to = newDragZone
+ )
updateDropTarget()
}
}
@@ -136,7 +140,7 @@ class DropTargetManager(
/** Stores the current drag state. */
private inner class DragState(
private val dragZones: List<DragZone>,
- draggedObject: DraggedObject
+ val draggedObject: DraggedObject
) {
val initialDragZone =
if (draggedObject.initialLocation.isOnLeft(isLayoutRtl)) {
@@ -157,7 +161,7 @@ class DropTargetManager(
fun onInitialDragZoneSet(dragZone: DragZone)
/** Called when the object was dragged to a different drag zone. */
- fun onDragZoneChanged(from: DragZone, to: DragZone)
+ fun onDragZoneChanged(draggedObject: DraggedObject, from: DragZone, to: DragZone)
/** Called when the drag has ended with the zone it ended in. */
fun onDragEnded(zone: DragZone)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 3e95a0b1100f..efc952644f0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -2644,12 +2644,7 @@ public class BubbleController implements ConfigurationChangeListener,
}
private void moveBubbleToFullscreen(String key) {
- Bubble b = mBubbleData.getBubbleInStackWithKey(key);
- if (b == null) {
- Log.w(TAG, "can't find bubble with key " + key + " to move to fullscreen");
- return;
- }
- b.getTaskView().moveToFullscreen();
+ // TODO b/388858013: convert the bubble to full screen
}
private boolean isDeviceLocked() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index bdb21f246359..3997412ab459 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -55,6 +55,7 @@ import com.android.wm.shell.shared.bubbles.DeviceConfig;
import com.android.wm.shell.shared.bubbles.DismissView;
import com.android.wm.shell.shared.bubbles.DragZone;
import com.android.wm.shell.shared.bubbles.DragZoneFactory;
+import com.android.wm.shell.shared.bubbles.DraggedObject;
import com.android.wm.shell.shared.bubbles.DropTargetManager;
import kotlin.Unit;
@@ -168,8 +169,8 @@ public class BubbleBarLayerView extends FrameLayout
}
@Override
- public void onDragZoneChanged(@NonNull DragZone from,
- @NonNull DragZone to) {
+ public void onDragZoneChanged(@NonNull DraggedObject draggedObject,
+ @NonNull DragZone from, @NonNull DragZone to) {
final boolean isBubbleLeft = to instanceof DragZone.Bubble.Left;
final boolean isBubbleRight = to instanceof DragZone.Bubble.Right;
if ((isBubbleLeft || isBubbleRight)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index dd2050a5fd5d..f73788486d04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -440,11 +440,18 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
statsToken);
}
- // In case of a hide, the statsToken should not been send yet (as the animation
- // is still ongoing). It will be sent at the end of the animation
- boolean hideAnimOngoing = !mImeRequestedVisible && mAnimation != null;
- setVisibleDirectly(mImeRequestedVisible || mAnimation != null,
- hideAnimOngoing ? null : statsToken);
+ boolean hideAnimOngoing;
+ boolean reportVisible;
+ if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
+ hideAnimOngoing = false;
+ reportVisible = mImeRequestedVisible;
+ } else {
+ // In case of a hide, the statsToken should not been send yet (as the animation
+ // is still ongoing). It will be sent at the end of the animation.
+ hideAnimOngoing = !mImeRequestedVisible && mAnimation != null;
+ reportVisible = mImeRequestedVisible || mAnimation != null;
+ }
+ setVisibleDirectly(reportVisible, hideAnimOngoing ? null : statsToken);
}
}
@@ -628,7 +635,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
+ " showing:" + (mAnimationDirection == DIRECTION_SHOW));
}
if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
- setAnimating(true);
+ setAnimating(true /* imeAnimationOngoing */);
}
int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY, defaultY),
imeTop(shownY, defaultY), mAnimationDirection == DIRECTION_SHOW,
@@ -678,7 +685,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
if (!android.view.inputmethod.Flags.refactorInsetsController()) {
dispatchEndPositioning(mDisplayId, mCancelled, t);
} else if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
- setAnimating(false);
+ setAnimating(false /* imeAnimationOngoing */);
}
if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
ImeTracker.forLogging().onProgress(mStatsToken,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index c3d15df6eae5..9c55f0ecda93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.windowdecor.tiling
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
@@ -28,9 +29,11 @@ import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import com.android.internal.annotations.VisibleForTesting
@@ -422,6 +425,8 @@ class DesktopTilingWindowDecoration(
change.taskInfo?.let {
if (it.isFullscreen || isMinimized(change.mode, info.type)) {
removeTaskIfTiled(it.taskId, /* taskVanished= */ false, it.isFullscreen)
+ } else if (isEnteringPip(change, info.type)) {
+ removeTaskIfTiled(it.taskId, /* taskVanished= */ true, it.isFullscreen)
}
}
}
@@ -434,6 +439,27 @@ class DesktopTilingWindowDecoration(
infoType == TRANSIT_OPEN))
}
+ private fun isEnteringPip(change: Change, transitType: Int): Boolean {
+ if (change.taskInfo != null && change.taskInfo?.windowingMode == WINDOWING_MODE_PINNED) {
+ // - TRANSIT_PIP: type (from RootWindowContainer)
+ // - TRANSIT_OPEN (from apps that enter PiP instantly on opening, mostly from
+ // CTS/Flicker tests).
+ // - TRANSIT_TO_FRONT, though uncommon with triggering PiP, should semantically also
+ // be allowed to animate if the task in question is pinned already - see b/308054074.
+ // - TRANSIT_CHANGE: This can happen if the request to enter PIP happens when we are
+ // collecting for another transition, such as TRANSIT_CHANGE (display rotation).
+ if (
+ transitType == TRANSIT_PIP ||
+ transitType == TRANSIT_OPEN ||
+ transitType == TRANSIT_TO_FRONT ||
+ transitType == TRANSIT_CHANGE
+ ) {
+ return true
+ }
+ }
+ return false
+ }
+
class AppResizingHelper(
val taskInfo: RunningTaskInfo,
val desktopModeWindowDecoration: DesktopModeWindowDecoration,
@@ -550,12 +576,16 @@ class DesktopTilingWindowDecoration(
taskVanished: Boolean = false,
shouldDelayUpdate: Boolean = false,
) {
+ val taskRepository = desktopUserRepositories.current
if (taskId == leftTaskResizingHelper?.taskInfo?.taskId) {
removeTask(leftTaskResizingHelper, taskVanished, shouldDelayUpdate)
leftTaskResizingHelper = null
- rightTaskResizingHelper
- ?.desktopModeWindowDecoration
- ?.updateDisabledResizingEdge(NONE, shouldDelayUpdate)
+ val taskId = rightTaskResizingHelper?.taskInfo?.taskId
+ if (taskId != null && taskRepository.isVisibleTask(taskId)) {
+ rightTaskResizingHelper
+ ?.desktopModeWindowDecoration
+ ?.updateDisabledResizingEdge(NONE, shouldDelayUpdate)
+ }
tearDownTiling()
return
}
@@ -563,9 +593,12 @@ class DesktopTilingWindowDecoration(
if (taskId == rightTaskResizingHelper?.taskInfo?.taskId) {
removeTask(rightTaskResizingHelper, taskVanished, shouldDelayUpdate)
rightTaskResizingHelper = null
- leftTaskResizingHelper
- ?.desktopModeWindowDecoration
- ?.updateDisabledResizingEdge(NONE, shouldDelayUpdate)
+ val taskId = leftTaskResizingHelper?.taskInfo?.taskId
+ if (taskId != null && taskRepository.isVisibleTask(taskId)) {
+ leftTaskResizingHelper
+ ?.desktopModeWindowDecoration
+ ?.updateDisabledResizingEdge(NONE, shouldDelayUpdate)
+ }
tearDownTiling()
}
}
@@ -600,7 +633,6 @@ class DesktopTilingWindowDecoration(
fun onOverviewAnimationStateChange(isRunning: Boolean) {
if (!isTilingManagerInitialised) return
-
if (isRunning) {
desktopTilingDividerWindowManager?.hideDividerBar()
} else if (allTiledTasksVisible()) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index c40a04c47b9b..b511fc34fa89 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -22,6 +22,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.content.ComponentName
import android.graphics.Rect
import android.view.Display.DEFAULT_DISPLAY
@@ -45,6 +46,17 @@ object DesktopTestHelpers {
.apply { bounds?.let { setBounds(it) } }
.build()
+ fun createPinnedTask(displayId: Int = DEFAULT_DISPLAY, bounds: Rect? = null): RunningTaskInfo =
+ TestRunningTaskInfoBuilder()
+ .setDisplayId(displayId)
+ .setParentTaskId(displayId)
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_PINNED)
+ .setLastActiveTime(100)
+ .apply { bounds?.let { setBounds(it) } }
+ .build()
+
fun createFullscreenTaskBuilder(displayId: Int = DEFAULT_DISPLAY): TestRunningTaskInfoBuilder =
TestRunningTaskInfoBuilder()
.setDisplayId(displayId)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
index 95498cbbe53c..3b21e365e911 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
@@ -352,7 +352,7 @@ class DropTargetManagerTest {
initialDragZone = dragZone
}
- override fun onDragZoneChanged(from: DragZone, to: DragZone) {
+ override fun onDragZoneChanged(draggedObject: DraggedObject, from: DragZone, to: DragZone) {
fromDragZone = from
toDragZone = to
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/RemoteTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/RemoteTransitionHandlerTest.kt
new file mode 100644
index 000000000000..048981d634ef
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/RemoteTransitionHandlerTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2025 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.wm.shell.transition
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.WindowManager
+import android.window.RemoteTransition
+import android.window.TransitionFilter
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestSyncExecutor
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/**
+ * Test class for [RemoteTransitionHandler].
+ *
+ * atest WMShellUnitTests:RemoteTransitionHandlerTest
+ */
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class RemoteTransitionHandlerTest : ShellTestCase() {
+
+ private val testExecutor: TestSyncExecutor = TestSyncExecutor()
+
+ private val testRemoteTransition = RemoteTransition(TestRemoteTransition())
+ private lateinit var handler: RemoteTransitionHandler
+
+ @Before
+ fun setUp() {
+ handler = RemoteTransitionHandler(testExecutor)
+ }
+
+ @Test
+ fun handleRequest_noRemoteTransition_returnsNull() {
+ val request = TransitionRequestInfo(WindowManager.TRANSIT_OPEN, null, null)
+
+ assertNull(handler.handleRequest(mock(), request))
+ }
+
+ @Test
+ fun handleRequest_testRemoteTransition_returnsWindowContainerTransaction() {
+ val request = TransitionRequestInfo(WindowManager.TRANSIT_OPEN, null, testRemoteTransition)
+
+ assertTrue(handler.handleRequest(mock(), request) is WindowContainerTransaction)
+ }
+
+ @Test
+ fun startAnimation_noRemoteTransition_returnsFalse() {
+ val request = TransitionRequestInfo(WindowManager.TRANSIT_OPEN, null, null)
+ handler.handleRequest(mock(), request)
+
+ val isHandled = handler.startAnimation(
+ /* transition= */ mock(),
+ /* info= */ createTransitionInfo(),
+ /* startTransaction= */ mock(),
+ /* finishTransaction= */ mock(),
+ /* finishCallback= */ {},
+ )
+
+ assertFalse(isHandled)
+ }
+
+ @Test
+ fun startAnimation_remoteTransition_returnsTrue() {
+ val request = TransitionRequestInfo(WindowManager.TRANSIT_OPEN, null, testRemoteTransition)
+ handler.addFiltered(TransitionFilter(), testRemoteTransition)
+ handler.handleRequest(mock(), request)
+
+ val isHandled = handler.startAnimation(
+ /* transition= */ testRemoteTransition.remoteTransition.asBinder(),
+ /* info= */ createTransitionInfo(),
+ /* startTransaction= */ mock(),
+ /* finishTransaction= */ mock(),
+ /* finishCallback= */ {},
+ )
+
+ assertTrue(isHandled)
+ }
+
+ private fun createTransitionInfo(
+ type: Int = WindowManager.TRANSIT_OPEN,
+ changeMode: Int = WindowManager.TRANSIT_CLOSE,
+ ): TransitionInfo =
+ TransitionInfo(type, /* flags= */ 0).apply {
+ addChange(
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = changeMode
+ parent = null
+ }
+ )
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
index bc8faedd77a9..e4424f3c57f2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor.tiling
import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.content.res.Resources
import android.graphics.Rect
@@ -24,8 +25,10 @@ import android.testing.AndroidTestingRunner
import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -40,6 +43,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeT
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createPinnedTask
import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler
@@ -552,6 +556,37 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
}
@Test
+ fun taskTiled_shouldBeRemoved_whenEnteringPip() {
+ val task1 = createPipTask()
+ val stableBounds = STABLE_BOUNDS_MOCK
+ whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(stableBounds)
+ }
+ whenever(context.resources).thenReturn(resources)
+ whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width)
+ whenever(tiledTaskHelper.taskInfo).thenReturn(task1)
+ whenever(tiledTaskHelper.desktopModeWindowDecoration).thenReturn(desktopWindowDecoration)
+ tilingDecoration.onAppTiled(
+ task1,
+ desktopWindowDecoration,
+ DesktopTasksController.SnapPosition.LEFT,
+ BOUNDS,
+ )
+ tilingDecoration.leftTaskResizingHelper = tiledTaskHelper
+ val changeInfo = createPipChangeTransition(task1)
+ tilingDecoration.onTransitionReady(
+ transition = mock(),
+ info = changeInfo,
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ assertThat(tilingDecoration.leftTaskResizingHelper).isNull()
+ verify(tiledTaskHelper, times(1)).dispose()
+ }
+
+ @Test
fun taskNotTiled_shouldNotBeRemoved_whenNotTiled() {
val task1 = createVisibleTask()
val task2 = createVisibleTask()
@@ -652,6 +687,23 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
whenever(userRepositories.current.isVisibleTask(eq(it.taskId))).thenReturn(true)
}
+ private fun createPipTask() =
+ createPinnedTask().also {
+ whenever(userRepositories.current.isVisibleTask(eq(it.taskId))).thenReturn(true)
+ }
+
+ private fun createPipChangeTransition(task: RunningTaskInfo?, type: Int = TRANSIT_PIP) =
+ TransitionInfo(type, /* flags= */ 0).apply {
+ addChange(
+ Change(mock(), mock()).apply {
+ mode = TRANSIT_PIP
+ parent = null
+ taskInfo = task
+ flags = flags
+ }
+ )
+ }
+
companion object {
private val NON_STABLE_BOUNDS_MOCK = Rect(50, 55, 100, 100)
private val STABLE_BOUNDS_MOCK = Rect(0, 0, 100, 100)
diff --git a/packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionLog.kt b/packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionLog.kt
new file mode 100644
index 000000000000..92455c05e3d7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionLog.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 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.settingslib.supervision
+
+/** Constants used in supervision logs. */
+object SupervisionLog {
+ const val TAG = "SupervisionSettings"
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionRestrictionsHelper.kt b/packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionRestrictionsHelper.kt
new file mode 100644
index 000000000000..1be8a17915a1
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionRestrictionsHelper.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2025 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.settingslib.supervision
+
+import android.app.admin.DeviceAdminReceiver
+import android.app.supervision.SupervisionManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import android.util.Log
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+
+/** Helper class for supervision-enforced restrictions. */
+object SupervisionRestrictionsHelper {
+
+ /**
+ * Creates an instance of [EnforcedAdmin] that uses the correct supervision component or returns
+ * null if supervision is not enabled.
+ */
+ @JvmStatic
+ fun createEnforcedAdmin(
+ context: Context,
+ restriction: String,
+ user: UserHandle,
+ ): EnforcedAdmin? {
+ val supervisionManager = context.getSystemService(SupervisionManager::class.java)
+ val supervisionAppPackage = supervisionManager?.activeSupervisionAppPackage ?: return null
+ var supervisionComponent: ComponentName? = null
+
+ // Try to find the service whose package matches the active supervision app.
+ val resolveSupervisionApps =
+ context.packageManager.queryIntentServicesAsUser(
+ Intent("android.app.action.BIND_SUPERVISION_APP_SERVICE"),
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS,
+ user.identifier,
+ )
+ resolveSupervisionApps
+ .mapNotNull { it.serviceInfo?.componentName }
+ .find { it.packageName == supervisionAppPackage }
+ ?.let { supervisionComponent = it }
+
+ if (supervisionComponent == null) {
+ // Try to find the PO receiver whose package matches the active supervision app, for
+ // backwards compatibility.
+ val resolveDeviceAdmins =
+ context.packageManager.queryBroadcastReceiversAsUser(
+ Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED),
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS,
+ user.identifier,
+ )
+ resolveDeviceAdmins
+ .mapNotNull { it.activityInfo?.componentName }
+ .find { it.packageName == supervisionAppPackage }
+ ?.let { supervisionComponent = it }
+ }
+
+ if (supervisionComponent == null) {
+ Log.d(SupervisionLog.TAG, "Could not find the supervision component.")
+ }
+ return EnforcedAdmin(supervisionComponent, restriction, user)
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java
index c5e6f60e3fa6..2b95fd1cdd9d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java
@@ -93,18 +93,10 @@ public class CreateUserActivity extends Activity {
@Override
public boolean onTouchEvent(@Nullable MotionEvent event) {
- onBackInvoked();
+ cancel();
return super.onTouchEvent(event);
}
- private void onBackInvoked() {
- if (mSetupUserDialog != null) {
- mSetupUserDialog.dismiss();
- }
- setResult(RESULT_CANCELED);
- finish();
- }
-
@VisibleForTesting
void setSuccessResult(String userName, Drawable userIcon, String path, Boolean isAdmin) {
Intent intent = new Intent(this, CreateUserActivity.class);
@@ -112,14 +104,12 @@ public class CreateUserActivity extends Activity {
intent.putExtra(EXTRA_IS_ADMIN, isAdmin);
intent.putExtra(EXTRA_USER_ICON_PATH, path);
- mSetupUserDialog.dismiss();
setResult(RESULT_OK, intent);
finish();
}
@VisibleForTesting
void cancel() {
- mSetupUserDialog.dismiss();
setResult(RESULT_CANCELED);
finish();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index d9f1b632323c..bce229f5a13d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -282,7 +282,7 @@ public class CreateUserDialogController {
mCustomDialogHelper.getDialog().dismiss();
break;
case EXIT_DIALOG:
- finish();
+ mUserCreationDialog.dismiss();
break;
default:
if (mCurrentState < EXIT_DIALOG) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/OWNERS b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/OWNERS
new file mode 100644
index 000000000000..04e7058b4384
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/OWNERS
@@ -0,0 +1 @@
+file:platform/frameworks/base:/core/java/android/app/supervision/OWNERS
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionIntentProviderTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionIntentProviderTest.kt
index 2ceed2875cb4..83ffa9399dc0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionIntentProviderTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionIntentProviderTest.kt
@@ -19,6 +19,7 @@ package com.android.settingslib.supervision
import android.app.supervision.SupervisionManager
import android.content.Context
import android.content.ContextWrapper
+import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -77,8 +78,8 @@ class SupervisionIntentProviderTest {
fun getSettingsIntent_unresolvedIntent() {
`when`(mockSupervisionManager.activeSupervisionAppPackage)
.thenReturn(SUPERVISION_APP_PACKAGE)
- `when`(mockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(), anyInt()))
- .thenReturn(emptyList())
+ `when`(mockPackageManager.queryIntentActivitiesAsUser(any<Intent>(), anyInt(), anyInt()))
+ .thenReturn(emptyList<ResolveInfo>())
val intent = SupervisionIntentProvider.getSettingsIntent(context)
@@ -89,7 +90,7 @@ class SupervisionIntentProviderTest {
fun getSettingsIntent_resolvedIntent() {
`when`(mockSupervisionManager.activeSupervisionAppPackage)
.thenReturn(SUPERVISION_APP_PACKAGE)
- `when`(mockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(), anyInt()))
+ `when`(mockPackageManager.queryIntentActivitiesAsUser(any<Intent>(), anyInt(), anyInt()))
.thenReturn(listOf(ResolveInfo()))
val intent = SupervisionIntentProvider.getSettingsIntent(context)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionRestrictionsHelperTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionRestrictionsHelperTest.kt
new file mode 100644
index 000000000000..872fc2a44b3d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionRestrictionsHelperTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2025 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.settingslib.supervision
+
+import android.app.admin.DeviceAdminReceiver
+import android.app.supervision.SupervisionManager
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatcher
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+/**
+ * Unit tests for [SupervisionRestrictionsHelper].
+ *
+ * Run with `atest SupervisionRestrictionsHelperTest`.
+ */
+@RunWith(AndroidJUnit4::class)
+class SupervisionRestrictionsHelperTest {
+ @get:Rule val mocks: MockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var mockPackageManager: PackageManager
+
+ @Mock private lateinit var mockSupervisionManager: SupervisionManager
+
+ private lateinit var context: Context
+
+ @Before
+ fun setUp() {
+ context =
+ object : ContextWrapper(InstrumentationRegistry.getInstrumentation().context) {
+ override fun getPackageManager() = mockPackageManager
+
+ override fun getSystemService(name: String) =
+ when (name) {
+ Context.SUPERVISION_SERVICE -> mockSupervisionManager
+ else -> super.getSystemService(name)
+ }
+ }
+ }
+
+ @Test
+ fun createEnforcedAdmin_nullSupervisionPackage() {
+ `when`(mockSupervisionManager.activeSupervisionAppPackage).thenReturn(null)
+
+ val enforcedAdmin =
+ SupervisionRestrictionsHelper.createEnforcedAdmin(context, RESTRICTION, USER_HANDLE)
+
+ assertThat(enforcedAdmin).isNull()
+ }
+
+ @Test
+ fun createEnforcedAdmin_supervisionAppService() {
+ val resolveInfo =
+ ResolveInfo().apply {
+ serviceInfo =
+ ServiceInfo().apply {
+ packageName = SUPERVISION_APP_PACKAGE
+ name = "service.class"
+ }
+ }
+
+ `when`(mockSupervisionManager.activeSupervisionAppPackage)
+ .thenReturn(SUPERVISION_APP_PACKAGE)
+ `when`(
+ mockPackageManager.queryIntentServicesAsUser(
+ argThat(hasAction("android.app.action.BIND_SUPERVISION_APP_SERVICE")),
+ anyInt(),
+ eq(USER_ID),
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+
+ val enforcedAdmin =
+ SupervisionRestrictionsHelper.createEnforcedAdmin(context, RESTRICTION, USER_HANDLE)
+
+ assertThat(enforcedAdmin).isNotNull()
+ assertThat(enforcedAdmin!!.component).isEqualTo(resolveInfo.serviceInfo.componentName)
+ assertThat(enforcedAdmin.enforcedRestriction).isEqualTo(RESTRICTION)
+ assertThat(enforcedAdmin.user).isEqualTo(USER_HANDLE)
+ }
+
+ @Test
+ fun createEnforcedAdmin_profileOwnerReceiver() {
+ val resolveInfo =
+ ResolveInfo().apply {
+ activityInfo =
+ ActivityInfo().apply {
+ packageName = SUPERVISION_APP_PACKAGE
+ name = "service.class"
+ }
+ }
+
+ `when`(mockSupervisionManager.activeSupervisionAppPackage)
+ .thenReturn(SUPERVISION_APP_PACKAGE)
+ `when`(mockPackageManager.queryIntentServicesAsUser(any<Intent>(), anyInt(), eq(USER_ID)))
+ .thenReturn(emptyList<ResolveInfo>())
+ `when`(
+ mockPackageManager.queryBroadcastReceiversAsUser(
+ argThat(hasAction(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED)),
+ anyInt(),
+ eq(USER_ID),
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+
+ val enforcedAdmin =
+ SupervisionRestrictionsHelper.createEnforcedAdmin(context, RESTRICTION, USER_HANDLE)
+
+ assertThat(enforcedAdmin).isNotNull()
+ assertThat(enforcedAdmin!!.component).isEqualTo(resolveInfo.activityInfo.componentName)
+ assertThat(enforcedAdmin.enforcedRestriction).isEqualTo(RESTRICTION)
+ assertThat(enforcedAdmin.user).isEqualTo(USER_HANDLE)
+ }
+
+ @Test
+ fun createEnforcedAdmin_noSupervisionComponent() {
+ `when`(mockSupervisionManager.activeSupervisionAppPackage)
+ .thenReturn(SUPERVISION_APP_PACKAGE)
+ `when`(mockPackageManager.queryIntentServicesAsUser(any<Intent>(), anyInt(), anyInt()))
+ .thenReturn(emptyList<ResolveInfo>())
+ `when`(mockPackageManager.queryBroadcastReceiversAsUser(any<Intent>(), anyInt(), anyInt()))
+ .thenReturn(emptyList<ResolveInfo>())
+
+ val enforcedAdmin =
+ SupervisionRestrictionsHelper.createEnforcedAdmin(context, RESTRICTION, USER_HANDLE)
+
+ assertThat(enforcedAdmin).isNotNull()
+ assertThat(enforcedAdmin!!.component).isNull()
+ assertThat(enforcedAdmin.enforcedRestriction).isEqualTo(RESTRICTION)
+ assertThat(enforcedAdmin.user).isEqualTo(USER_HANDLE)
+ }
+
+ private fun hasAction(action: String) =
+ object : ArgumentMatcher<Intent> {
+ override fun matches(intent: Intent?) = intent?.action == action
+ }
+
+ private companion object {
+ const val SUPERVISION_APP_PACKAGE = "app.supervision"
+ const val RESTRICTION = "restriction"
+ val USER_HANDLE = UserHandle.CURRENT
+ val USER_ID = USER_HANDLE.identifier
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java
index f58eb7cc2e31..220c03e53be2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java
@@ -65,23 +65,23 @@ public class CreateUserActivityTest {
}
@Test
- public void onTouchEvent_dismissesDialogAndCancelsResult() {
+ public void onTouchEvent_finishesActivityAndCancelsResult() {
mCreateUserActivity.onTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0,
0));
- assertThat(mCreateUserActivity.mSetupUserDialog.isShowing()).isFalse();
+ assertThat(mCreateUserActivity.isFinishing()).isTrue();
assertThat(shadowOf(mCreateUserActivity).getResultCode())
.isEqualTo(Activity.RESULT_CANCELED);
}
@Test
- public void setSuccessResult_dismissesDialogAndSetsSuccessResult() {
+ public void setSuccessResult_finishesActivityAndSetsSuccessResult() {
Drawable mockDrawable = mock(Drawable.class);
mCreateUserActivity.setSuccessResult(TEST_USER_NAME, mockDrawable, TEST_USER_ICON_PATH,
TEST_IS_ADMIN);
- assertThat(mCreateUserActivity.mSetupUserDialog.isShowing()).isFalse();
+ assertThat(mCreateUserActivity.isFinishing()).isTrue();
assertThat(shadowOf(mCreateUserActivity).getResultCode()).isEqualTo(Activity.RESULT_OK);
Intent resultIntent = shadowOf(mCreateUserActivity).getResultIntent();
@@ -92,10 +92,10 @@ public class CreateUserActivityTest {
}
@Test
- public void cancel_dismissesDialogAndSetsCancelResult() {
+ public void cancel_finishesActivityAndSetsCancelResult() {
mCreateUserActivity.cancel();
- assertThat(mCreateUserActivity.mSetupUserDialog.isShowing()).isFalse();
+ assertThat(mCreateUserActivity.isFinishing()).isTrue();
assertThat(shadowOf(mCreateUserActivity).getResultCode())
.isEqualTo(Activity.RESULT_CANCELED);
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 72ae76a45cac..f628a420d8fa 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -312,6 +312,9 @@
<!-- Permission necessary to change car audio volume through CarAudioManager -->
<uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
+ <!-- To detect when projecting to Android Auto -->
+ <uses-permission android:name="android.permission.READ_PROJECTION_STATE" />
+
<!-- Permission to control Android Debug Bridge (ADB) -->
<uses-permission android:name="android.permission.MANAGE_DEBUGGING" />
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index e43b8a0b9297..7ee6a6e5ebf4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -107,6 +107,16 @@ constructor(
*/
// TODO(b/301385865): Remove this flag.
private val disableWmTimeout: Boolean = false,
+
+ /**
+ * Whether we should disable the reparent transaction that puts the opening/closing window above
+ * the view's window. This should be set to true in tests only, where we can't currently use a
+ * valid leash.
+ *
+ * TODO(b/397180418): Remove this flag when we don't have the RemoteAnimation wrapper anymore
+ * and we can just inject a fake transaction.
+ */
+ private val skipReparentTransaction: Boolean = false,
) {
@JvmOverloads
constructor(
@@ -1140,6 +1150,7 @@ constructor(
DelegatingAnimationCompletionListener(listener, this::dispose),
transitionAnimator,
disableWmTimeout,
+ skipReparentTransaction,
)
}
@@ -1173,6 +1184,16 @@ constructor(
*/
// TODO(b/301385865): Remove this flag.
disableWmTimeout: Boolean = false,
+
+ /**
+ * Whether we should disable the reparent transaction that puts the opening/closing window
+ * above the view's window. This should be set to true in tests only, where we can't
+ * currently use a valid leash.
+ *
+ * TODO(b/397180418): Remove this flag when we don't have the RemoteAnimation wrapper
+ * anymore and we can just inject a fake transaction.
+ */
+ private val skipReparentTransaction: Boolean = false,
) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> {
private val transitionContainer = controller.transitionContainer
private val context = transitionContainer.context
@@ -1515,7 +1536,7 @@ constructor(
)
}
- if (moveTransitionAnimationLayer()) {
+ if (moveTransitionAnimationLayer() && !skipReparentTransaction) {
// Ensure that the launching window is rendered above the view's window,
// so it is not obstructed.
// TODO(b/397180418): re-use the start transaction once the
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 5d9c441db003..a4a96d19e8bb 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -1006,13 +1006,32 @@ class TransitionAnimator(
Log.d(TAG, "Animation ended")
}
- // TODO(b/330672236): Post this to the main thread instead so that it does not
- // flicker with Flexiglass enabled.
- controller.onTransitionAnimationEnd(isExpandingFullyAbove)
- transitionContainerOverlay.remove(windowBackgroundLayer)
+ val onEnd = {
+ controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+ transitionContainerOverlay.remove(windowBackgroundLayer)
- if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
- openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+ if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
+ openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+ }
+ }
+ // TODO(b/330672236): Post this to the main thread for launches as well, so that they do not
+ // flicker with Flexiglass enabled.
+ if (controller.isLaunching) {
+ onEnd()
+ } else {
+ // onAnimationEnd is called at the end of the animation, on a Choreographer animation
+ // tick. During dialog launches, the following calls will move the animated content from
+ // the dialog overlay back to its original position, and this change must be reflected
+ // in the next frame given that we then sync the next frame of both the content and
+ // dialog ViewRoots. During SysUI activity launches, we will instantly collapse the
+ // shade at the end of the transition. However, if those are rendered by Compose, whose
+ // compositions are also scheduled on a Choreographer frame, any state change made
+ // *right now* won't be reflected in the next frame given that a Choreographer frame
+ // can't schedule another and have it happen in the same frame. So we post the forwarded
+ // calls to [Controller.onLaunchAnimationEnd] in the main executor, leaving this
+ // Choreographer frame, ensuring that any state change applied by
+ // onTransitionAnimationEnd() will be reflected in the same frame.
+ mainExecutor.execute { onEnd() }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
index ed73d89db2c7..6a25069a4e5e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
@@ -73,12 +73,12 @@ class CommunalOngoingContentStartableTest : SysuiTestCase() {
assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
- kosmos.setCommunalEnabled(true)
+ setCommunalEnabled(true)
assertThat(fakeCommunalMediaRepository.isListening()).isTrue()
assertThat(fakeCommunalSmartspaceRepository.isListening()).isTrue()
- kosmos.setCommunalEnabled(false)
+ setCommunalEnabled(false)
assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
@@ -93,13 +93,13 @@ class CommunalOngoingContentStartableTest : SysuiTestCase() {
assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
- kosmos.setCommunalEnabled(true)
+ setCommunalEnabled(true)
// Media listening does not start when UMO is disabled.
assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
assertThat(fakeCommunalSmartspaceRepository.isListening()).isTrue()
- kosmos.setCommunalEnabled(false)
+ setCommunalEnabled(false)
assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryImplTest.kt
new file mode 100644
index 000000000000..f9b29e9bc5b5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryImplTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2025 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.communal.data.repository
+
+import android.app.UiModeManager
+import android.app.UiModeManager.OnProjectionStateChangedListener
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CarProjectionRepositoryImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val capturedListeners = mutableListOf<OnProjectionStateChangedListener>()
+
+ private val Kosmos.uiModeManager by
+ Kosmos.Fixture<UiModeManager> {
+ mock {
+ on {
+ addOnProjectionStateChangedListener(
+ eq(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE),
+ any(),
+ any(),
+ )
+ } doAnswer
+ {
+ val listener = it.getArgument<OnProjectionStateChangedListener>(2)
+ capturedListeners.add(listener)
+ Unit
+ }
+
+ on { removeOnProjectionStateChangedListener(any()) } doAnswer
+ {
+ val listener = it.getArgument<OnProjectionStateChangedListener>(0)
+ capturedListeners.remove(listener)
+ Unit
+ }
+
+ on { activeProjectionTypes } doReturn UiModeManager.PROJECTION_TYPE_NONE
+ }
+ }
+
+ private val Kosmos.underTest by
+ Kosmos.Fixture {
+ CarProjectionRepositoryImpl(
+ uiModeManager = uiModeManager,
+ bgDispatcher = testDispatcher,
+ )
+ }
+
+ @Test
+ fun testProjectionActiveUpdatesAfterCallback() =
+ kosmos.runTest {
+ val projectionActive by collectLastValue(underTest.projectionActive)
+ assertThat(projectionActive).isFalse()
+
+ setActiveProjectionType(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE)
+ assertThat(projectionActive).isTrue()
+
+ setActiveProjectionType(UiModeManager.PROJECTION_TYPE_NONE)
+ assertThat(projectionActive).isFalse()
+ }
+
+ @Test
+ fun testProjectionInitialValueTrue() =
+ kosmos.runTest {
+ setActiveProjectionType(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE)
+
+ val projectionActive by collectLastValue(underTest.projectionActive)
+ assertThat(projectionActive).isTrue()
+ }
+
+ @Test
+ fun testUnsubscribeWhenCancelled() =
+ kosmos.runTest {
+ val job = underTest.projectionActive.launchIn(backgroundScope)
+ assertThat(capturedListeners).hasSize(1)
+
+ job.cancel()
+ assertThat(capturedListeners).isEmpty()
+ }
+
+ private fun Kosmos.setActiveProjectionType(@UiModeManager.ProjectionType projectionType: Int) {
+ uiModeManager.stub { on { activeProjectionTypes } doReturn projectionType }
+ capturedListeners.forEach { it.onProjectionStateChanged(projectionType, emptySet()) }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index 5c983656225e..09d44a5e18d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -34,9 +34,7 @@ import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.communal.data.model.DisabledReason
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING
-import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.WhenToDream
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
@@ -202,63 +200,6 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
@EnableFlags(FLAG_COMMUNAL_HUB)
@Test
- fun secondaryUserIsInvalid() =
- kosmos.runTest {
- val enabledState by collectLastValue(underTest.getEnabledState(SECONDARY_USER))
-
- assertThat(enabledState?.enabled).isFalse()
- assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_INVALID_USER)
- }
-
- @EnableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
- @Test
- fun classicFlagIsDisabled() =
- kosmos.runTest {
- setCommunalV2Enabled(false)
- val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
- assertThat(enabledState?.enabled).isFalse()
- assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG)
- }
-
- @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
- @Test
- fun communalHubFlagIsDisabled() =
- kosmos.runTest {
- val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
- assertThat(enabledState?.enabled).isFalse()
- assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG)
- }
-
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
- fun hubIsDisabledByUser() =
- kosmos.runTest {
- fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
- val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
- assertThat(enabledState?.enabled).isFalse()
- assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_USER_SETTING)
-
- fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, SECONDARY_USER.id)
- assertThat(enabledState?.enabled).isFalse()
-
- fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, PRIMARY_USER.id)
- assertThat(enabledState?.enabled).isTrue()
- }
-
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
- fun hubIsDisabledByDevicePolicy() =
- kosmos.runTest {
- val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
- assertThat(enabledState?.enabled).isTrue()
-
- setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
- assertThat(enabledState?.enabled).isFalse()
- assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_DEVICE_POLICY)
- }
-
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
fun widgetsAllowedForWorkProfile_isFalse_whenDisallowedByDevicePolicy() =
kosmos.runTest {
val widgetsAllowedForWorkProfile by
@@ -269,36 +210,6 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
assertThat(widgetsAllowedForWorkProfile).isFalse()
}
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
- fun hubIsEnabled_whenDisallowedByDevicePolicyForWorkProfile() =
- kosmos.runTest {
- val enabledStateForPrimaryUser by
- collectLastValue(underTest.getEnabledState(PRIMARY_USER))
- assertThat(enabledStateForPrimaryUser?.enabled).isTrue()
-
- setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_WIDGETS_ALL)
- assertThat(enabledStateForPrimaryUser?.enabled).isTrue()
- }
-
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
- fun hubIsDisabledByUserAndDevicePolicy() =
- kosmos.runTest {
- val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
- assertThat(enabledState?.enabled).isTrue()
-
- fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
- setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
-
- assertThat(enabledState?.enabled).isFalse()
- assertThat(enabledState)
- .containsExactly(
- DisabledReason.DISABLED_REASON_DEVICE_POLICY,
- DisabledReason.DISABLED_REASON_USER_SETTING,
- )
- }
-
@Test
@DisableFlags(FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
fun backgroundType_defaultValue() =
@@ -327,26 +238,6 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
}
@Test
- fun screensaverDisabledByUser() =
- kosmos.runTest {
- val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))
-
- fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, 0, PRIMARY_USER.id)
-
- assertThat(enabledState).isFalse()
- }
-
- @Test
- fun screensaverEnabledByUser() =
- kosmos.runTest {
- val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))
-
- fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, 1, PRIMARY_USER.id)
-
- assertThat(enabledState).isTrue()
- }
-
- @Test
fun whenToDream_charging() =
kosmos.runTest {
val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorTest.kt
new file mode 100644
index 000000000000..fc4cd43577b1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2025 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.communal.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.carProjectionRepository
+import com.android.systemui.communal.data.repository.fake
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CarProjectionInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val Kosmos.underTest by Kosmos.Fixture { carProjectionInteractor }
+
+ @Test
+ fun testProjectionActive() =
+ kosmos.runTest {
+ val projectionActive by collectLastValue(underTest.projectionActive)
+ assertThat(projectionActive).isFalse()
+
+ carProjectionRepository.fake.setProjectionActive(true)
+ assertThat(projectionActive).isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorTest.kt
new file mode 100644
index 000000000000..f4a1c90a5471
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2025 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.communal.domain.interactor
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
+import com.android.systemui.communal.data.model.FEATURE_AUTO_OPEN
+import com.android.systemui.communal.data.model.FEATURE_MANUAL_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
+import com.android.systemui.communal.posturing.data.repository.fake
+import com.android.systemui.communal.posturing.data.repository.posturingRepository
+import com.android.systemui.communal.posturing.shared.model.PosturedState
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository.Companion.MAIN_USER_ID
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalAutoOpenInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val Kosmos.underTest by Kosmos.Fixture { communalAutoOpenInteractor }
+
+ @Before
+ fun setUp() {
+ runBlocking { kosmos.fakeUserRepository.asMainUser() }
+ with(kosmos.fakeSettings) {
+ putBoolForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, false, MAIN_USER_ID)
+ putBoolForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, false, MAIN_USER_ID)
+ putBoolForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, false, MAIN_USER_ID)
+ }
+ }
+
+ @Test
+ fun testStartWhileCharging() =
+ kosmos.runTest {
+ val shouldAutoOpen by collectLastValue(underTest.shouldAutoOpen)
+ val suppressionReason by collectLastValue(underTest.suppressionReason)
+
+ fakeSettings.putBoolForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ true,
+ MAIN_USER_ID,
+ )
+
+ batteryRepository.fake.setDevicePluggedIn(false)
+ assertThat(shouldAutoOpen).isFalse()
+ assertThat(suppressionReason)
+ .isEqualTo(
+ SuppressionReason.ReasonWhenToAutoShow(FEATURE_AUTO_OPEN or FEATURE_MANUAL_OPEN)
+ )
+
+ batteryRepository.fake.setDevicePluggedIn(true)
+ assertThat(shouldAutoOpen).isTrue()
+ assertThat(suppressionReason).isNull()
+ }
+
+ @Test
+ fun testStartWhileDocked() =
+ kosmos.runTest {
+ val shouldAutoOpen by collectLastValue(underTest.shouldAutoOpen)
+ val suppressionReason by collectLastValue(underTest.suppressionReason)
+
+ fakeSettings.putBoolForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ true,
+ MAIN_USER_ID,
+ )
+
+ batteryRepository.fake.setDevicePluggedIn(true)
+ fakeDockManager.setIsDocked(false)
+
+ assertThat(shouldAutoOpen).isFalse()
+ assertThat(suppressionReason)
+ .isEqualTo(
+ SuppressionReason.ReasonWhenToAutoShow(FEATURE_AUTO_OPEN or FEATURE_MANUAL_OPEN)
+ )
+
+ fakeDockManager.setIsDocked(true)
+ fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
+ assertThat(shouldAutoOpen).isTrue()
+ assertThat(suppressionReason).isNull()
+ }
+
+ @Test
+ fun testStartWhilePostured() =
+ kosmos.runTest {
+ val shouldAutoOpen by collectLastValue(underTest.shouldAutoOpen)
+ val suppressionReason by collectLastValue(underTest.suppressionReason)
+
+ fakeSettings.putBoolForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+ true,
+ MAIN_USER_ID,
+ )
+
+ batteryRepository.fake.setDevicePluggedIn(true)
+ posturingRepository.fake.setPosturedState(PosturedState.NotPostured)
+
+ assertThat(shouldAutoOpen).isFalse()
+ assertThat(suppressionReason)
+ .isEqualTo(
+ SuppressionReason.ReasonWhenToAutoShow(FEATURE_AUTO_OPEN or FEATURE_MANUAL_OPEN)
+ )
+
+ posturingRepository.fake.setPosturedState(PosturedState.Postured(1f))
+ assertThat(shouldAutoOpen).isTrue()
+ assertThat(suppressionReason).isNull()
+ }
+
+ @Test
+ fun testStartNever() =
+ kosmos.runTest {
+ val shouldAutoOpen by collectLastValue(underTest.shouldAutoOpen)
+ val suppressionReason by collectLastValue(underTest.suppressionReason)
+
+ fakeSettings.putBoolForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ false,
+ MAIN_USER_ID,
+ )
+ fakeSettings.putBoolForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ false,
+ MAIN_USER_ID,
+ )
+ fakeSettings.putBoolForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+ false,
+ MAIN_USER_ID,
+ )
+
+ batteryRepository.fake.setDevicePluggedIn(true)
+ posturingRepository.fake.setPosturedState(PosturedState.Postured(1f))
+ fakeDockManager.setIsDocked(true)
+
+ assertThat(shouldAutoOpen).isFalse()
+ assertThat(suppressionReason)
+ .isEqualTo(
+ SuppressionReason.ReasonWhenToAutoShow(FEATURE_AUTO_OPEN or FEATURE_MANUAL_OPEN)
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
deleted file mode 100644
index beec184b80e7..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2024 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.communal.domain.interactor
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
-import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
-import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/**
- * This class is a variation of the [CommunalInteractorTest] for cases where communal is disabled.
- */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalInteractorCommunalDisabledTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
-
- private lateinit var communalRepository: FakeCommunalSceneRepository
- private lateinit var widgetRepository: FakeCommunalWidgetRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
-
- private lateinit var underTest: CommunalInteractor
-
- @Before
- fun setUp() {
- communalRepository = kosmos.fakeCommunalSceneRepository
- widgetRepository = kosmos.fakeCommunalWidgetRepository
- keyguardRepository = kosmos.fakeKeyguardRepository
-
- mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB)
-
- underTest = kosmos.communalInteractor
- }
-
- @Test
- fun isCommunalEnabled_false() =
- testScope.runTest { assertThat(underTest.isCommunalEnabled.value).isFalse() }
-
- @Test
- fun isCommunalAvailable_whenStorageUnlock_false() =
- testScope.runTest {
- val isCommunalAvailable by collectLastValue(underTest.isCommunalAvailable)
-
- assertThat(isCommunalAvailable).isFalse()
-
- keyguardRepository.setIsEncryptedOrLockdown(false)
- runCurrent()
-
- assertThat(isCommunalAvailable).isFalse()
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 8424746f3db5..b65ecf46dcca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -21,7 +21,6 @@ import android.app.admin.DevicePolicyManager
import android.app.admin.devicePolicyManager
import android.content.Intent
import android.content.pm.UserInfo
-import android.content.res.mainResources
import android.os.UserHandle
import android.os.UserManager
import android.os.userManager
@@ -39,9 +38,8 @@ import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.common.data.repository.batteryRepository
-import com.android.systemui.common.data.repository.fake
import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
+import com.android.systemui.communal.data.model.SuppressionReason
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
@@ -50,14 +48,9 @@ import com.android.systemui.communal.data.repository.fakeCommunalTutorialReposit
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
-import com.android.systemui.communal.posturing.data.repository.fake
-import com.android.systemui.communal.posturing.data.repository.posturingRepository
-import com.android.systemui.communal.posturing.shared.model.PosturedState
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.EditModeState
-import com.android.systemui.dock.DockManager
-import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -75,19 +68,16 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.statusbar.phone.fakeManagedProfileController
import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.advanceTimeBy
-import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -98,10 +88,6 @@ import org.mockito.Mockito.verify
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
-/**
- * This class of test cases assume that communal is enabled. For disabled cases, see
- * [CommunalInteractorCommunalDisabledTest].
- */
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@@ -109,10 +95,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
private val secondaryUser = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
- private val kosmos =
- testKosmos()
- .apply { mainResources = mContext.orCreateTestableResources.resources }
- .useUnconfinedTestDispatcher()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val Kosmos.underTest by Kosmos.Fixture { communalInteractor }
@@ -128,104 +111,40 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
-
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault,
- false,
- )
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault,
- false,
- )
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault,
- false,
- )
- }
-
- @After
- fun tearDown() {
- mContext.orCreateTestableResources.removeOverride(
- com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault
- )
- mContext.orCreateTestableResources.removeOverride(
- com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault
- )
- mContext.orCreateTestableResources.removeOverride(
- com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault
- )
}
@Test
fun communalEnabled_true() =
kosmos.runTest {
- fakeUserRepository.setSelectedUserInfo(mainUser)
+ communalSettingsInteractor.setSuppressionReasons(emptyList())
assertThat(underTest.isCommunalEnabled.value).isTrue()
}
@Test
- fun isCommunalAvailable_mainUserUnlockedAndMainUser_true() =
+ fun isCommunalAvailable_whenKeyguardShowing_true() =
kosmos.runTest {
- val isAvailable by collectLastValue(underTest.isCommunalAvailable)
- assertThat(isAvailable).isFalse()
-
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
- fakeUserRepository.setSelectedUserInfo(mainUser)
- fakeKeyguardRepository.setKeyguardShowing(true)
-
- assertThat(isAvailable).isTrue()
- }
+ communalSettingsInteractor.setSuppressionReasons(emptyList())
+ fakeKeyguardRepository.setKeyguardShowing(false)
- @Test
- fun isCommunalAvailable_mainUserLockedAndMainUser_false() =
- kosmos.runTest {
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
assertThat(isAvailable).isFalse()
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, false)
- fakeUserRepository.setSelectedUserInfo(mainUser)
fakeKeyguardRepository.setKeyguardShowing(true)
-
- assertThat(isAvailable).isFalse()
+ assertThat(isAvailable).isTrue()
}
@Test
- fun isCommunalAvailable_mainUserUnlockedAndSecondaryUser_false() =
+ fun isCommunalAvailable_suppressed() =
kosmos.runTest {
- val isAvailable by collectLastValue(underTest.isCommunalAvailable)
- assertThat(isAvailable).isFalse()
-
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
- fakeUserRepository.setSelectedUserInfo(secondaryUser)
+ communalSettingsInteractor.setSuppressionReasons(emptyList())
fakeKeyguardRepository.setKeyguardShowing(true)
- assertThat(isAvailable).isFalse()
- }
-
- @Test
- fun isCommunalAvailable_whenKeyguardShowing_true() =
- kosmos.runTest {
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
- assertThat(isAvailable).isFalse()
-
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
- fakeUserRepository.setSelectedUserInfo(mainUser)
- fakeKeyguardRepository.setKeyguardShowing(true)
-
assertThat(isAvailable).isTrue()
- }
-
- @Test
- fun isCommunalAvailable_communalDisabled_false() =
- kosmos.runTest {
- mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
- val isAvailable by collectLastValue(underTest.isCommunalAvailable)
- assertThat(isAvailable).isFalse()
-
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, false)
- fakeUserRepository.setSelectedUserInfo(mainUser)
- fakeKeyguardRepository.setKeyguardShowing(true)
+ communalSettingsInteractor.setSuppressionReasons(
+ listOf(SuppressionReason.ReasonUnknown())
+ )
assertThat(isAvailable).isFalse()
}
@@ -1280,66 +1199,6 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
.inOrder()
}
- @Test
- fun showCommunalWhileCharging() =
- kosmos.runTest {
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
- fakeUserRepository.setSelectedUserInfo(mainUser)
- fakeKeyguardRepository.setKeyguardShowing(true)
- fakeSettings.putIntForUser(
- Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
- 1,
- mainUser.id,
- )
-
- val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
- batteryRepository.fake.setDevicePluggedIn(false)
- assertThat(shouldShowCommunal).isFalse()
-
- batteryRepository.fake.setDevicePluggedIn(true)
- assertThat(shouldShowCommunal).isTrue()
- }
-
- @Test
- fun showCommunalWhilePosturedAndCharging() =
- kosmos.runTest {
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
- fakeUserRepository.setSelectedUserInfo(mainUser)
- fakeKeyguardRepository.setKeyguardShowing(true)
- fakeSettings.putIntForUser(
- Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
- 1,
- mainUser.id,
- )
-
- val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
- batteryRepository.fake.setDevicePluggedIn(true)
- posturingRepository.fake.setPosturedState(PosturedState.NotPostured)
- assertThat(shouldShowCommunal).isFalse()
-
- posturingRepository.fake.setPosturedState(PosturedState.Postured(1f))
- assertThat(shouldShowCommunal).isTrue()
- }
-
- @Test
- fun showCommunalWhileDocked() =
- kosmos.runTest {
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
- fakeUserRepository.setSelectedUserInfo(mainUser)
- fakeKeyguardRepository.setKeyguardShowing(true)
- fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, 1, mainUser.id)
-
- batteryRepository.fake.setDevicePluggedIn(true)
- fakeDockManager.setIsDocked(false)
-
- val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
- assertThat(shouldShowCommunal).isFalse()
-
- fakeDockManager.setIsDocked(true)
- fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
- assertThat(shouldShowCommunal).isTrue()
- }
-
private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
.thenReturn(disabledFlags)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 8dc7a331dc2d..b8dbc9f77076 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -32,9 +32,9 @@ import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_DIRECT_EDIT_MODE
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
-import com.android.systemui.common.data.repository.batteryRepository
-import com.android.systemui.common.data.repository.fake
import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
+import com.android.systemui.communal.data.model.FEATURE_MANUAL_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository
@@ -50,6 +50,7 @@ import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalEnabled
import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalMetricsLogger
@@ -101,7 +102,6 @@ import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.advanceTimeBy
@@ -128,7 +128,9 @@ import platform.test.runner.parameterized.Parameters
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
+
@Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
+
@Mock private lateinit var metricsLogger: CommunalMetricsLogger
private val kosmos = testKosmos()
@@ -212,11 +214,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() =
testScope.runTest {
- // Keyguard showing, storage unlocked, main user, and tutorial not started.
keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- userRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
- setIsMainUser(true)
+ kosmos.setCommunalEnabled(true)
tutorialRepository.setTutorialSettingState(
Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
)
@@ -951,21 +950,16 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
fun swipeToCommunal() =
kosmos.runTest {
setCommunalV2ConfigEnabled(true)
- val mainUser = fakeUserRepository.asMainUser()
- fakeKeyguardRepository.setKeyguardShowing(true)
- fakeUserRepository.setUserUnlocked(mainUser.id, true)
- fakeUserTracker.set(userInfos = listOf(mainUser), selectedUserIndex = 0)
- fakeSettings.putIntForUser(
- Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
- 1,
- mainUser.id,
+ // Suppress manual opening
+ communalSettingsInteractor.setSuppressionReasons(
+ listOf(SuppressionReason.ReasonUnknown(FEATURE_MANUAL_OPEN))
)
val viewModel = createViewModel()
val swipeToHubEnabled by collectLastValue(viewModel.swipeToHubEnabled)
assertThat(swipeToHubEnabled).isFalse()
- batteryRepository.fake.setDevicePluggedIn(true)
+ communalSettingsInteractor.setSuppressionReasons(emptyList())
assertThat(swipeToHubEnabled).isTrue()
keyguardTransitionRepository.sendTransitionStep(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index c15f797aad5d..df10d058c5d1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.communal.widgets
import android.content.pm.UserInfo
-import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
@@ -25,6 +24,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalEnabled
import com.android.systemui.communal.shared.model.FakeGlanceableHubMultiUserHelper
import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
import com.android.systemui.coroutines.collectLastValue
@@ -37,11 +37,9 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository.Companion.MAIN_USER_ID
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.user.domain.interactor.userLockedInteractor
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.test.runCurrent
@@ -282,22 +280,12 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
}
}
- private suspend fun setCommunalAvailable(
- available: Boolean,
- setKeyguardShowing: Boolean = true,
- ) =
+ private fun setCommunalAvailable(available: Boolean, setKeyguardShowing: Boolean = true) =
with(kosmos) {
- fakeUserRepository.setUserUnlocked(MAIN_USER_ID, true)
- fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ setCommunalEnabled(available)
if (setKeyguardShowing) {
fakeKeyguardRepository.setKeyguardShowing(true)
}
- val settingsValue = if (available) 1 else 0
- fakeSettings.putIntForUser(
- Settings.Secure.GLANCEABLE_HUB_ENABLED,
- settingsValue,
- MAIN_USER_INFO.id,
- )
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index 6c4325adced4..046d92d58978 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -35,7 +35,6 @@ package com.android.systemui.keyguard.domain.interactor
import android.os.PowerManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
@@ -43,8 +42,6 @@ import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
-import com.android.systemui.common.data.repository.batteryRepository
-import com.android.systemui.common.data.repository.fake
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.setCommunalV2Available
@@ -72,7 +69,6 @@ import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.testKosmos
-import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.runBlocking
@@ -433,9 +429,7 @@ class FromAodTransitionInteractorTest : SysuiTestCase() {
@EnableFlags(FLAG_GLANCEABLE_HUB_V2)
fun testTransitionToGlanceableHub_onWakeUpFromAod() =
kosmos.runTest {
- val user = setCommunalV2Available(true)
- fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1, user.id)
- batteryRepository.fake.setDevicePluggedIn(true)
+ setCommunalV2Available(true)
val currentScene by collectLastValue(communalSceneInteractor.currentScene)
fakeCommunalSceneRepository.changeScene(CommunalScenes.Blank)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index 9be786fab34d..096c3dafd01c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -193,6 +193,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
@Test
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun testTransitionToLockscreen_onWake_canNotDream_glanceableHubAvailable() =
kosmos.runTest {
whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 8df70ef0fd2e..7d5e9a5ed178 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -33,7 +33,7 @@ import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepositor
import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSceneTransitionInteractor
-import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2Available
import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -1004,16 +1004,15 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@BrokenWithSceneContainer(339465026)
fun occludedToGlanceableHub_communalKtfRefactor() =
testScope.runTest {
- // GIVEN a device on lockscreen and communal is available
- keyguardRepository.setKeyguardShowing(true)
- kosmos.setCommunalAvailable(true)
- runCurrent()
-
// GIVEN a prior transition has run to OCCLUDED from GLANCEABLE_HUB
runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
+ // GIVEN a device on lockscreen and communal is available
+ kosmos.setCommunalV2Available(true)
+ runCurrent()
+
// WHEN occlusion ends
keyguardRepository.setKeyguardOccluded(false)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt
index b82f5fce9e14..ff2e13b51e14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt
@@ -81,7 +81,7 @@ class SysUIStateDispatcherTest : SysuiTestCase() {
private companion object {
const val DISPLAY_1 = 1
const val DISPLAY_2 = 2
- const val FLAG_1 = 10L
- const val FLAG_2 = 20L
+ const val FLAG_1 = 1L
+ const val FLAG_2 = 2L
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt
index 09588f9f3751..d1b552906fbb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt
@@ -35,10 +35,10 @@ class SysUiStateExtTest : SysuiTestCase() {
@Test
fun updateFlags() {
- underTest.updateFlags(Display.DEFAULT_DISPLAY, 1L to true, 2L to false, 3L to true)
+ underTest.updateFlags(Display.DEFAULT_DISPLAY, 1L to true, 2L to false, 4L to true)
assertThat(underTest.flags and 1L).isNotEqualTo(0L)
assertThat(underTest.flags and 2L).isEqualTo(0L)
- assertThat(underTest.flags and 3L).isNotEqualTo(0L)
+ assertThat(underTest.flags and 4L).isNotEqualTo(0L)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index f54c28f4295b..72b003f9f463 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -82,7 +82,6 @@ import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.log.LogWtfHandlerRule;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.UserTracker;
@@ -108,7 +107,6 @@ import kotlinx.coroutines.flow.StateFlow;
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -207,8 +205,6 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
private final FakeExecutor mBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
private final Executor mMainExecutor = Runnable::run; // Direct executor
- @Rule public final LogWtfHandlerRule wtfHandlerRule = new LogWtfHandlerRule();
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSuppressionStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSuppressionStartable.kt
new file mode 100644
index 000000000000..6a611ec5b647
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSuppressionStartable.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2025 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.communal
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.data.model.SuppressionReason
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@SysUISingleton
+class CommunalSuppressionStartable
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val suppressionFlows: Set<@JvmSuppressWildcards Flow<SuppressionReason?>>,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
+ @CommunalTableLog private val tableLogBuffer: TableLogBuffer,
+) : CoreStartable {
+ override fun start() {
+ getSuppressionReasons()
+ .onEach { reasons -> communalSettingsInteractor.setSuppressionReasons(reasons) }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnName = "suppressionReasons",
+ initialValue = emptyList(),
+ )
+ .flowOn(bgDispatcher)
+ .launchIn(applicationScope)
+ }
+
+ private fun getSuppressionReasons(): Flow<List<SuppressionReason>> {
+ if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
+ return flowOf(listOf(SuppressionReason.ReasonFlagDisabled))
+ }
+ return combine(suppressionFlows) { reasons -> reasons.filterNotNull() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index bb3be531aa8a..a31c0bd35453 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -29,6 +29,7 @@ import com.android.systemui.communal.data.repository.CommunalSmartspaceRepositor
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor
+import com.android.systemui.communal.domain.suppression.dagger.CommunalSuppressionModule
import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.communal.shared.log.CommunalStatsLogProxyImpl
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -70,6 +71,7 @@ import kotlinx.coroutines.CoroutineScope
CommunalSmartspaceRepositoryModule::class,
CommunalStartableModule::class,
GlanceableHubWidgetManagerModule::class,
+ CommunalSuppressionModule::class,
]
)
interface CommunalModule {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
index 7358aa7b3fcd..a4f75e81b6ae 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
@@ -22,6 +22,7 @@ import com.android.systemui.communal.CommunalDreamStartable
import com.android.systemui.communal.CommunalMetricsStartable
import com.android.systemui.communal.CommunalOngoingContentStartable
import com.android.systemui.communal.CommunalSceneStartable
+import com.android.systemui.communal.CommunalSuppressionStartable
import com.android.systemui.communal.DevicePosturingListener
import com.android.systemui.communal.log.CommunalLoggerStartable
import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
@@ -73,4 +74,9 @@ interface CommunalStartableModule {
@IntoMap
@ClassKey(DevicePosturingListener::class)
fun bindDevicePosturingistener(impl: DevicePosturingListener): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(CommunalSuppressionStartable::class)
+ fun bindCommunalSuppressionStartable(impl: CommunalSuppressionStartable): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt
deleted file mode 100644
index 83a5bdb14ebd..000000000000
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2024 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.communal.data.model
-
-import com.android.systemui.log.table.Diffable
-import com.android.systemui.log.table.TableRowLogger
-import java.util.EnumSet
-
-/** Reasons that communal is disabled, primarily for logging. */
-enum class DisabledReason(val loggingString: String) {
- /** Communal should be disabled due to invalid current user */
- DISABLED_REASON_INVALID_USER("invalidUser"),
- /** Communal should be disabled due to the flag being off */
- DISABLED_REASON_FLAG("flag"),
- /** Communal should be disabled because the user has turned off the setting */
- DISABLED_REASON_USER_SETTING("userSetting"),
- /** Communal is disabled by the device policy app */
- DISABLED_REASON_DEVICE_POLICY("devicePolicy"),
-}
-
-/**
- * Model representing the reasons communal hub should be disabled. Allows logging reasons separately
- * for debugging.
- */
-@JvmInline
-value class CommunalEnabledState(
- private val disabledReasons: EnumSet<DisabledReason> =
- EnumSet.noneOf(DisabledReason::class.java)
-) : Diffable<CommunalEnabledState>, Set<DisabledReason> by disabledReasons {
-
- /** Creates [CommunalEnabledState] with a single reason for being disabled */
- constructor(reason: DisabledReason) : this(EnumSet.of(reason))
-
- /** Checks if there are any reasons communal should be disabled. If none, returns true. */
- val enabled: Boolean
- get() = isEmpty()
-
- override fun logDiffs(prevVal: CommunalEnabledState, row: TableRowLogger) {
- for (reason in DisabledReason.entries) {
- val newVal = contains(reason)
- if (newVal != prevVal.contains(reason)) {
- row.logChange(
- columnName = reason.loggingString,
- value = newVal,
- )
- }
- }
- }
-
- override fun logFull(row: TableRowLogger) {
- for (reason in DisabledReason.entries) {
- row.logChange(columnName = reason.loggingString, value = contains(reason))
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalFeature.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalFeature.kt
new file mode 100644
index 000000000000..5fb1c4e84eef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalFeature.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 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.communal.data.model
+
+import android.annotation.IntDef
+
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+ flag = true,
+ prefix = ["FEATURE_"],
+ value = [FEATURE_AUTO_OPEN, FEATURE_MANUAL_OPEN, FEATURE_ENABLED, FEATURE_ALL],
+)
+annotation class CommunalFeature
+
+/** If we should automatically open the hub */
+const val FEATURE_AUTO_OPEN: Int = 1
+
+/** If the user is allowed to manually open the hub */
+const val FEATURE_MANUAL_OPEN: Int = 1 shl 1
+
+/**
+ * If the hub should be considered enabled. If not, it may be cleaned up entirely to reduce memory
+ * footprint.
+ */
+const val FEATURE_ENABLED: Int = 1 shl 2
+
+const val FEATURE_ALL: Int = FEATURE_ENABLED or FEATURE_MANUAL_OPEN or FEATURE_AUTO_OPEN
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/SuppressionReason.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/SuppressionReason.kt
new file mode 100644
index 000000000000..de05bed7ef57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/SuppressionReason.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2025 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.communal.data.model
+
+sealed interface SuppressionReason {
+ @CommunalFeature val suppressedFeatures: Int
+
+ /** Whether this reason suppresses a particular feature. */
+ fun isSuppressed(@CommunalFeature feature: Int): Boolean {
+ return (suppressedFeatures and feature) != 0
+ }
+
+ /** Suppress hub automatically opening due to Android Auto projection */
+ data object ReasonCarProjection : SuppressionReason {
+ override val suppressedFeatures: Int = FEATURE_AUTO_OPEN
+ }
+
+ /** Suppress hub due to the "When to dream" conditions not being met */
+ data class ReasonWhenToAutoShow(override val suppressedFeatures: Int) : SuppressionReason
+
+ /** Suppress hub due to device policy */
+ data object ReasonDevicePolicy : SuppressionReason {
+ override val suppressedFeatures: Int = FEATURE_ALL
+ }
+
+ /** Suppress hub due to the user disabling the setting */
+ data object ReasonSettingDisabled : SuppressionReason {
+ override val suppressedFeatures: Int = FEATURE_ALL
+ }
+
+ /** Suppress hub due to the user being locked */
+ data object ReasonUserLocked : SuppressionReason {
+ override val suppressedFeatures: Int = FEATURE_ALL
+ }
+
+ /** Suppress hub due the a secondary user being active */
+ data object ReasonSecondaryUser : SuppressionReason {
+ override val suppressedFeatures: Int = FEATURE_ALL
+ }
+
+ /** Suppress hub due to the flag being disabled */
+ data object ReasonFlagDisabled : SuppressionReason {
+ override val suppressedFeatures: Int = FEATURE_ALL
+ }
+
+ /** Suppress hub due to an unknown reason, used as initial state and in tests */
+ data class ReasonUnknown(override val suppressedFeatures: Int = FEATURE_ALL) :
+ SuppressionReason
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CarProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CarProjectionRepository.kt
new file mode 100644
index 000000000000..4fe641a78d4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CarProjectionRepository.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2025 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.communal.data.repository
+
+import android.app.UiModeManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+interface CarProjectionRepository {
+ /** Whether car projection is active. */
+ val projectionActive: Flow<Boolean>
+
+ /**
+ * Checks the system for the current car projection state.
+ *
+ * @return True if projection is active, false otherwise.
+ */
+ suspend fun isProjectionActive(): Boolean
+}
+
+@SysUISingleton
+class CarProjectionRepositoryImpl
+@Inject
+constructor(
+ private val uiModeManager: UiModeManager,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+) : CarProjectionRepository {
+ override val projectionActive: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val listener =
+ UiModeManager.OnProjectionStateChangedListener { _, _ -> trySend(Unit) }
+ uiModeManager.addOnProjectionStateChangedListener(
+ UiModeManager.PROJECTION_TYPE_AUTOMOTIVE,
+ bgDispatcher.asExecutor(),
+ listener,
+ )
+ awaitClose { uiModeManager.removeOnProjectionStateChangedListener(listener) }
+ }
+ .emitOnStart()
+ .map { isProjectionActive() }
+ .flowOn(bgDispatcher)
+
+ override suspend fun isProjectionActive(): Boolean =
+ withContext(bgDispatcher) {
+ (uiModeManager.activeProjectionTypes and UiModeManager.PROJECTION_TYPE_AUTOMOTIVE) != 0
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
index 7f137f3b976b..0d590db97860 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
@@ -22,4 +22,6 @@ import dagger.Module
@Module
interface CommunalRepositoryModule {
@Binds fun communalRepository(impl: CommunalSceneRepositoryImpl): CommunalSceneRepository
+
+ @Binds fun carProjectionRepository(impl: CarProjectionRepositoryImpl): CarProjectionRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index 4c291a0c5a2e..6f688d172843 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -26,12 +26,9 @@ import android.provider.Settings
import com.android.systemui.Flags.communalHub
import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.communal.data.model.CommunalEnabledState
-import com.android.systemui.communal.data.model.DisabledReason
-import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_DEVICE_POLICY
-import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_FLAG
-import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_INVALID_USER
-import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_USER_SETTING
+import com.android.systemui.communal.data.model.CommunalFeature
+import com.android.systemui.communal.data.model.FEATURE_ALL
+import com.android.systemui.communal.data.model.SuppressionReason
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule.Companion.DEFAULT_BACKGROUND_TYPE
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.WhenToDream
@@ -43,22 +40,23 @@ import com.android.systemui.flags.Flags
import com.android.systemui.util.kotlin.emitOnStart
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
-import java.util.EnumSet
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
interface CommunalSettingsRepository {
- /** A [CommunalEnabledState] for the specified user. */
- fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState>
+ /** Whether a particular feature is enabled */
+ fun isEnabled(@CommunalFeature feature: Int): Flow<Boolean>
- fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean>
+ /**
+ * Suppresses the hub with the given reasons. If there are no reasons, the hub will not be
+ * suppressed.
+ */
+ fun setSuppressionReasons(reasons: List<SuppressionReason>)
/**
* Returns a [WhenToDream] for the specified user, indicating what state the device should be in
@@ -66,6 +64,9 @@ interface CommunalSettingsRepository {
*/
fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream>
+ /** Returns whether glanceable hub is enabled by the current user. */
+ fun getSettingEnabledByUser(user: UserInfo): Flow<Boolean>
+
/**
* Returns true if any glanceable hub functionality should be enabled via configs and flags.
*
@@ -123,6 +124,19 @@ constructor(
resources.getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault)
}
+ private val _suppressionReasons =
+ MutableStateFlow<List<SuppressionReason>>(
+ // Suppress hub by default until we get an initial update.
+ listOf(SuppressionReason.ReasonUnknown(FEATURE_ALL))
+ )
+
+ override fun isEnabled(@CommunalFeature feature: Int): Flow<Boolean> =
+ _suppressionReasons.map { reasons -> reasons.none { it.isSuppressed(feature) } }
+
+ override fun setSuppressionReasons(reasons: List<SuppressionReason>) {
+ _suppressionReasons.value = reasons
+ }
+
override fun getFlagEnabled(): Boolean {
return if (getV2FlagEnabled()) {
true
@@ -138,44 +152,6 @@ constructor(
glanceableHubV2()
}
- override fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState> {
- if (!user.isMain) {
- return flowOf(CommunalEnabledState(DISABLED_REASON_INVALID_USER))
- }
- if (!getFlagEnabled()) {
- return flowOf(CommunalEnabledState(DISABLED_REASON_FLAG))
- }
- return combine(
- getEnabledByUser(user).mapToReason(DISABLED_REASON_USER_SETTING),
- getAllowedByDevicePolicy(user).mapToReason(DISABLED_REASON_DEVICE_POLICY),
- ) { reasons ->
- reasons.filterNotNull()
- }
- .map { reasons ->
- if (reasons.isEmpty()) {
- EnumSet.noneOf(DisabledReason::class.java)
- } else {
- EnumSet.copyOf(reasons)
- }
- }
- .map { reasons -> CommunalEnabledState(reasons) }
- .flowOn(bgDispatcher)
- }
-
- override fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> =
- secureSettings
- .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.SCREENSAVER_ENABLED))
- // Force an update
- .onStart { emit(Unit) }
- .map {
- secureSettings.getIntForUser(
- Settings.Secure.SCREENSAVER_ENABLED,
- SCREENSAVER_ENABLED_SETTING_DEFAULT,
- user.id,
- ) == 1
- }
- .flowOn(bgDispatcher)
-
override fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream> =
secureSettings
.observerFlow(
@@ -247,11 +223,11 @@ constructor(
?: defaultBackgroundType
}
- private fun getEnabledByUser(user: UserInfo): Flow<Boolean> =
+ override fun getSettingEnabledByUser(user: UserInfo): Flow<Boolean> =
secureSettings
.observerFlow(userId = user.id, names = arrayOf(Settings.Secure.GLANCEABLE_HUB_ENABLED))
// Force an update
- .onStart { emit(Unit) }
+ .emitOnStart()
.map {
secureSettings.getIntForUser(
Settings.Secure.GLANCEABLE_HUB_ENABLED,
@@ -259,17 +235,13 @@ constructor(
user.id,
) == 1
}
+ .flowOn(bgDispatcher)
companion object {
const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background"
private const val ENABLED_SETTING_DEFAULT = 1
- private const val SCREENSAVER_ENABLED_SETTING_DEFAULT = 0
}
}
private fun DevicePolicyManager.areKeyguardWidgetsAllowed(userId: Int): Boolean =
(getKeyguardDisabledFeatures(null, userId) and KEYGUARD_DISABLE_WIDGETS_ALL) == 0
-
-private fun Flow<Boolean>.mapToReason(reason: DisabledReason) = map { enabled ->
- if (enabled) null else reason
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractor.kt
new file mode 100644
index 000000000000..17b61e1c6fdf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractor.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2025 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.communal.domain.interactor
+
+import com.android.systemui.communal.data.repository.CarProjectionRepository
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class CarProjectionInteractor @Inject constructor(repository: CarProjectionRepository) {
+ /** Whether car projection is active. */
+ val projectionActive = repository.projectionActive
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractor.kt
new file mode 100644
index 000000000000..51df3338a18e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractor.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2025 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.communal.domain.interactor
+
+import com.android.systemui.common.domain.interactor.BatteryInteractor
+import com.android.systemui.communal.dagger.CommunalModule.Companion.SWIPE_TO_HUB
+import com.android.systemui.communal.data.model.FEATURE_AUTO_OPEN
+import com.android.systemui.communal.data.model.FEATURE_MANUAL_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
+import com.android.systemui.communal.posturing.domain.interactor.PosturingInteractor
+import com.android.systemui.communal.shared.model.WhenToDream
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.retrieveIsDocked
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class CommunalAutoOpenInteractor
+@Inject
+constructor(
+ communalSettingsInteractor: CommunalSettingsInteractor,
+ @Background private val backgroundContext: CoroutineContext,
+ private val batteryInteractor: BatteryInteractor,
+ private val posturingInteractor: PosturingInteractor,
+ private val dockManager: DockManager,
+ @Named(SWIPE_TO_HUB) private val allowSwipeAlways: Boolean,
+) {
+ val shouldAutoOpen: Flow<Boolean> =
+ communalSettingsInteractor.whenToDream
+ .flatMapLatestConflated { whenToDream ->
+ when (whenToDream) {
+ WhenToDream.WHILE_CHARGING -> batteryInteractor.isDevicePluggedIn
+ WhenToDream.WHILE_DOCKED -> {
+ allOf(batteryInteractor.isDevicePluggedIn, dockManager.retrieveIsDocked())
+ }
+ WhenToDream.WHILE_POSTURED -> {
+ allOf(batteryInteractor.isDevicePluggedIn, posturingInteractor.postured)
+ }
+ WhenToDream.NEVER -> flowOf(false)
+ }
+ }
+ .flowOn(backgroundContext)
+
+ val suppressionReason: Flow<SuppressionReason?> =
+ shouldAutoOpen.map { conditionMet ->
+ if (conditionMet) {
+ null
+ } else {
+ var suppressedFeatures = FEATURE_AUTO_OPEN
+ if (!allowSwipeAlways) {
+ suppressedFeatures = suppressedFeatures or FEATURE_MANUAL_OPEN
+ }
+ SuppressionReason.ReasonWhenToAutoShow(suppressedFeatures)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 564628d3f52f..684c52ad45f3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -30,13 +30,11 @@ import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.Flags.communalResponsiveGrid
import com.android.systemui.Flags.glanceableHubBlurredBackground
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.domain.interactor.BatteryInteractor
import com.android.systemui.communal.data.repository.CommunalMediaRepository
import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent
-import com.android.systemui.communal.posturing.domain.interactor.PosturingInteractor
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.FULL
@@ -45,14 +43,11 @@ import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.shared.model.EditModeState
-import com.android.systemui.communal.shared.model.WhenToDream
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dock.DockManager
-import com.android.systemui.dock.retrieveIsDocked
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -69,11 +64,8 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.ManagedProfileController
-import com.android.systemui.user.domain.interactor.UserLockedInteractor
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
-import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.emitOnStart
-import com.android.systemui.util.kotlin.isDevicePluggedIn
import javax.inject.Inject
import kotlin.time.Duration.Companion.minutes
import kotlinx.coroutines.CoroutineDispatcher
@@ -125,10 +117,6 @@ constructor(
@CommunalLog logBuffer: LogBuffer,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
private val managedProfileController: ManagedProfileController,
- private val batteryInteractor: BatteryInteractor,
- private val dockManager: DockManager,
- private val posturingInteractor: PosturingInteractor,
- private val userLockedInteractor: UserLockedInteractor,
) {
private val logger = Logger(logBuffer, "CommunalInteractor")
@@ -162,11 +150,7 @@ constructor(
/** Whether communal features are enabled and available. */
val isCommunalAvailable: Flow<Boolean> =
- allOf(
- communalSettingsInteractor.isCommunalEnabled,
- userLockedInteractor.isUserUnlocked(userManager.mainUser),
- keyguardInteractor.isKeyguardShowing,
- )
+ allOf(communalSettingsInteractor.isCommunalEnabled, keyguardInteractor.isKeyguardShowing)
.distinctUntilChanged()
.onEach { available ->
logger.i({ "Communal is ${if (bool1) "" else "un"}available" }) {
@@ -184,37 +168,6 @@ constructor(
replay = 1,
)
- /**
- * Whether communal hub should be shown automatically, depending on the user's [WhenToDream]
- * state.
- */
- val shouldShowCommunal: StateFlow<Boolean> =
- allOf(
- isCommunalAvailable,
- communalSettingsInteractor.whenToDream
- .flatMapLatest { whenToDream ->
- when (whenToDream) {
- WhenToDream.NEVER -> flowOf(false)
-
- WhenToDream.WHILE_CHARGING -> batteryInteractor.isDevicePluggedIn
-
- WhenToDream.WHILE_DOCKED ->
- allOf(
- batteryInteractor.isDevicePluggedIn,
- dockManager.retrieveIsDocked(),
- )
-
- WhenToDream.WHILE_POSTURED ->
- allOf(
- batteryInteractor.isDevicePluggedIn,
- posturingInteractor.postured,
- )
- }
- }
- .flowOn(bgDispatcher),
- )
- .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
-
private val _isDisclaimerDismissed = MutableStateFlow(false)
val isDisclaimerDismissed: Flow<Boolean> = _isDisclaimerDismissed.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index a0b1261df346..ae89b39175c1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -18,17 +18,18 @@ package com.android.systemui.communal.domain.interactor
import android.content.pm.UserInfo
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.communal.data.model.CommunalEnabledState
+import com.android.systemui.communal.data.model.FEATURE_AUTO_OPEN
+import com.android.systemui.communal.data.model.FEATURE_ENABLED
+import com.android.systemui.communal.data.model.FEATURE_MANUAL_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
import com.android.systemui.communal.data.repository.CommunalSettingsRepository
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.WhenToDream
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.log.dagger.CommunalTableLog
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.settings.UserTracker
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -53,33 +54,43 @@ constructor(
private val repository: CommunalSettingsRepository,
userInteractor: SelectedUserInteractor,
private val userTracker: UserTracker,
- @CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
- /** Whether or not communal is enabled for the currently selected user. */
+ /** Whether communal is enabled at all. */
val isCommunalEnabled: StateFlow<Boolean> =
- userInteractor.selectedUserInfo
- .flatMapLatest { user -> repository.getEnabledState(user) }
- .logDiffsForTable(
- tableLogBuffer = tableLogBuffer,
- columnPrefix = "disabledReason",
- initialValue = CommunalEnabledState(),
- )
- .map { model -> model.enabled }
- // Start this eagerly since the value is accessed synchronously in many places.
+ repository
+ .isEnabled(FEATURE_ENABLED)
.stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
- /** Whether or not screensaver (dreams) is enabled for the currently selected user. */
- val isScreensaverEnabled: Flow<Boolean> =
- userInteractor.selectedUserInfo.flatMapLatest { user ->
- repository.getScreensaverEnabledState(user)
- }
+ /** Whether manually opening the hub is enabled */
+ val manualOpenEnabled: StateFlow<Boolean> =
+ repository
+ .isEnabled(FEATURE_MANUAL_OPEN)
+ .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
+
+ /** Whether auto-opening the hub is enabled */
+ val autoOpenEnabled: StateFlow<Boolean> =
+ repository
+ .isEnabled(FEATURE_AUTO_OPEN)
+ .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
/** When to dream for the currently selected user. */
val whenToDream: Flow<WhenToDream> =
- userInteractor.selectedUserInfo.flatMapLatest { user ->
+ userInteractor.selectedUserInfo.flatMapLatestConflated { user ->
repository.getWhenToDreamState(user)
}
+ /** Whether communal hub is allowed by device policy for the current user */
+ val allowedForCurrentUserByDevicePolicy: Flow<Boolean> =
+ userInteractor.selectedUserInfo.flatMapLatestConflated { user ->
+ repository.getAllowedByDevicePolicy(user)
+ }
+
+ /** Whether the hub is enabled for the current user */
+ val settingEnabledForCurrentUser: Flow<Boolean> =
+ userInteractor.selectedUserInfo.flatMapLatestConflated { user ->
+ repository.getSettingEnabledByUser(user)
+ }
+
/**
* Returns true if any glanceable hub functionality should be enabled via configs and flags.
*
@@ -109,6 +120,14 @@ constructor(
*/
fun isV2FlagEnabled(): Boolean = repository.getV2FlagEnabled()
+ /**
+ * Suppresses the hub with the given reasons. If there are no reasons, the hub will not be
+ * suppressed.
+ */
+ fun setSuppressionReasons(reasons: List<SuppressionReason>) {
+ repository.setSuppressionReasons(reasons)
+ }
+
/** The type of background to use for the hub. Used to experiment with different backgrounds */
val communalBackground: Flow<CommunalBackgroundType> =
userInteractor.selectedUserInfo
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/FlowExt.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/FlowExt.kt
new file mode 100644
index 000000000000..a10e90f09cc2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/FlowExt.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2025 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.communal.domain.suppression
+
+import com.android.systemui.communal.data.model.SuppressionReason
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+fun Flow<Boolean>.mapToReasonIfNotAllowed(reason: SuppressionReason): Flow<SuppressionReason?> =
+ this.map { allowed -> if (allowed) null else reason }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/dagger/CommunalSuppressionModule.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/dagger/CommunalSuppressionModule.kt
new file mode 100644
index 000000000000..c62d77eee287
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/dagger/CommunalSuppressionModule.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2025 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.communal.domain.suppression.dagger
+
+import com.android.systemui.Flags.glanceableHubV2
+import com.android.systemui.communal.data.model.SuppressionReason
+import com.android.systemui.communal.domain.interactor.CarProjectionInteractor
+import com.android.systemui.communal.domain.interactor.CommunalAutoOpenInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.domain.suppression.mapToReasonIfNotAllowed
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.user.domain.interactor.UserLockedInteractor
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+import dagger.multibindings.Multibinds
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+@Module
+interface CommunalSuppressionModule {
+ /**
+ * A set of reasons why communal may be suppressed. Ensures that this can be injected even if
+ * it's empty.
+ */
+ @Multibinds fun suppressorSet(): Set<Flow<SuppressionReason?>>
+
+ companion object {
+ @Provides
+ @IntoSet
+ fun provideCarProjectionSuppressor(
+ interactor: CarProjectionInteractor
+ ): Flow<SuppressionReason?> {
+ if (!glanceableHubV2()) {
+ return flowOf(null)
+ }
+ return not(interactor.projectionActive)
+ .mapToReasonIfNotAllowed(SuppressionReason.ReasonCarProjection)
+ }
+
+ @Provides
+ @IntoSet
+ fun provideDevicePolicySuppressor(
+ interactor: CommunalSettingsInteractor
+ ): Flow<SuppressionReason?> {
+ return interactor.allowedForCurrentUserByDevicePolicy.mapToReasonIfNotAllowed(
+ SuppressionReason.ReasonDevicePolicy
+ )
+ }
+
+ @Provides
+ @IntoSet
+ fun provideSettingDisabledSuppressor(
+ interactor: CommunalSettingsInteractor
+ ): Flow<SuppressionReason?> {
+ return interactor.settingEnabledForCurrentUser.mapToReasonIfNotAllowed(
+ SuppressionReason.ReasonSettingDisabled
+ )
+ }
+
+ @Provides
+ @IntoSet
+ fun bindUserLockedSuppressor(interactor: UserLockedInteractor): Flow<SuppressionReason?> {
+ return interactor.currentUserUnlocked.mapToReasonIfNotAllowed(
+ SuppressionReason.ReasonUserLocked
+ )
+ }
+
+ @Provides
+ @IntoSet
+ fun provideAutoOpenSuppressor(
+ interactor: CommunalAutoOpenInteractor
+ ): Flow<SuppressionReason?> {
+ return interactor.suppressionReason
+ }
+
+ @Provides
+ @IntoSet
+ fun provideMainUserSuppressor(
+ interactor: SelectedUserInteractor
+ ): Flow<SuppressionReason?> {
+ return interactor.selectedUserInfo
+ .map { it.isMain }
+ .mapToReasonIfNotAllowed(SuppressionReason.ReasonSecondaryUser)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 62a98d7a48ea..857fa5cac3e2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -369,12 +369,10 @@ constructor(
val swipeToHubEnabled: Flow<Boolean> by lazy {
val inAllowedDeviceState =
- if (swipeToHub) {
- MutableStateFlow(true)
- } else if (v2FlagEnabled()) {
- communalInteractor.shouldShowCommunal
+ if (v2FlagEnabled()) {
+ communalSettingsInteractor.manualOpenEnabled
} else {
- MutableStateFlow(false)
+ MutableStateFlow(swipeToHub)
}
if (v2FlagEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index a7c078f235b4..36b75c6fc6b8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -61,7 +61,7 @@ constructor(
fun startTransitionFromDream() {
val showGlanceableHub =
if (communalSettingsInteractor.isV2FlagEnabled()) {
- communalInteractor.shouldShowCommunal.value
+ communalSettingsInteractor.autoOpenEnabled.value
} else {
communalInteractor.isCommunalEnabled.value &&
!keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index ef06a85bd0d9..54af8f5b9806 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -20,7 +20,6 @@ import android.animation.ValueAnimator
import android.util.Log
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -60,7 +59,6 @@ constructor(
private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
private val communalSettingsInteractor: CommunalSettingsInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
- private val communalInteractor: CommunalInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.AOD,
@@ -110,7 +108,7 @@ constructor(
val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
val biometricUnlockMode = keyguardInteractor.biometricUnlockState.value.mode
val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
- val shouldShowCommunal = communalInteractor.shouldShowCommunal.value
+ val shouldShowCommunal = communalSettingsInteractor.autoOpenEnabled.value
if (!maybeHandleInsecurePowerGesture()) {
val shouldTransitionToLockscreen =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 6f5f662d6fa3..1fc41085f772 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -153,7 +153,7 @@ constructor(
.filterRelevantKeyguardStateAnd { isAwake -> isAwake }
.sample(
communalInteractor.isCommunalAvailable,
- communalInteractor.shouldShowCommunal,
+ communalSettingsInteractor.autoOpenEnabled,
)
.collect { (_, isCommunalAvailable, shouldShowCommunal) ->
val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
@@ -209,7 +209,7 @@ constructor(
powerInteractor.detailedWakefulness
.filterRelevantKeyguardStateAnd { it.isAwake() }
.sample(
- communalInteractor.shouldShowCommunal,
+ communalSettingsInteractor.autoOpenEnabled,
communalInteractor.isCommunalAvailable,
keyguardInteractor.biometricUnlockState,
wakeToGoneInteractor.canWakeDirectlyToGone,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 0fb98ffa4a30..3b1b6fcc45f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -115,7 +115,7 @@ constructor(
powerInteractor.isAwake
.debounce(50L)
.filterRelevantKeyguardStateAnd { isAwake -> isAwake }
- .sample(communalInteractor.shouldShowCommunal)
+ .sample(communalSettingsInteractor.autoOpenEnabled)
.collect { shouldShowCommunal ->
if (shouldShowCommunal) {
// This case handles tapping the power button to transition through
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index a01dc02bbd9f..f8c7a86687dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,7 +20,6 @@ import android.animation.ValueAnimator
import android.util.MathUtils
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -69,7 +68,6 @@ constructor(
private val shadeRepository: ShadeRepository,
powerInteractor: PowerInteractor,
private val communalSettingsInteractor: CommunalSettingsInteractor,
- private val communalInteractor: CommunalInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
private val swipeToDismissInteractor: SwipeToDismissInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -355,7 +353,7 @@ constructor(
private fun listenForLockscreenToGlanceableHubV2() {
scope.launch {
- communalInteractor.shouldShowCommunal
+ communalSettingsInteractor.autoOpenEnabled
.filterRelevantKeyguardStateAnd { shouldShow -> shouldShow }
.collect {
communalSceneInteractor.changeScene(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt
index 0e8e595f5b06..848d8221e4db 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt
@@ -26,6 +26,10 @@ import android.widget.SeekBar
import android.widget.TextView
import androidx.constraintlayout.widget.Barrier
import com.android.internal.widget.CachingIconView
+import com.android.systemui.FontStyles.GSF_HEADLINE_SMALL
+import com.android.systemui.FontStyles.GSF_LABEL_LARGE
+import com.android.systemui.FontStyles.GSF_LABEL_MEDIUM
+import com.android.systemui.FontStyles.GSF_TITLE_MEDIUM
import com.android.systemui.res.R
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
import com.android.systemui.surfaceeffects.ripple.MultiRippleView
@@ -177,9 +181,9 @@ class MediaViewHolder constructor(itemView: View) {
R.id.touch_ripple_view,
)
- val headlineSmallTF: Typeface = Typeface.create("gsf-headline-small", Typeface.NORMAL)
- val titleMediumTF: Typeface = Typeface.create("gsf-title-medium", Typeface.NORMAL)
- val labelMediumTF: Typeface = Typeface.create("gsf-label-medium", Typeface.NORMAL)
- val labelLargeTF: Typeface = Typeface.create("gsf-label-large", Typeface.NORMAL)
+ val headlineSmallTF: Typeface = Typeface.create(GSF_HEADLINE_SMALL, Typeface.NORMAL)
+ val titleMediumTF: Typeface = Typeface.create(GSF_TITLE_MEDIUM, Typeface.NORMAL)
+ val labelMediumTF: Typeface = Typeface.create(GSF_LABEL_MEDIUM, Typeface.NORMAL)
+ val labelLargeTF: Typeface = Typeface.create(GSF_LABEL_LARGE, Typeface.NORMAL)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt b/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt
index aaed606f8fb2..cec846e84f01 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt
@@ -43,8 +43,6 @@ class StateChange {
return this
}
- fun hasChanges() = flagsToSet != 0L || flagsToClear != 0L
-
/**
* Applies all changed flags to [sysUiState].
*
@@ -83,6 +81,7 @@ class StateChange {
iterateBits(flagsToSet or flagsToClear) { bit -> sysUiState.setFlag(bit, false) }
}
+ /** Resets all the pending changes. */
fun clear() {
flagsToSet = 0
flagsToClear = 0
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
index e99ee7ddb919..71cb74543485 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
@@ -22,6 +22,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.PerDisplayInstanceProviderWithTeardown
import com.android.systemui.dump.DumpManager
import com.android.systemui.model.SysUiState.SysUiStateCallback
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
import dagger.assisted.Assisted
@@ -29,6 +30,7 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dalvik.annotation.optimization.NeverCompile
import java.io.PrintWriter
+import java.lang.Long.bitCount
import javax.inject.Inject
/** Contains sysUi state flags and notifies registered listeners whenever changes happen. */
@@ -111,8 +113,7 @@ constructor(
get() = _flags
private var _flags: Long = 0
- private var flagsToSet: Long = 0
- private var flagsToClear: Long = 0
+ private val stateChange = StateChange()
/**
* Add listener to be notified of changes made to SysUI state. The callback will also be called
@@ -132,13 +133,11 @@ constructor(
/** Methods to this call can be chained together before calling [.commitUpdate]. */
override fun setFlag(@SystemUiStateFlags flag: Long, enabled: Boolean): SysUiState {
- val toSet = flagWithOptionalOverrides(flag, enabled, displayId, sceneContainerPlugin)
-
- if (toSet) {
- flagsToSet = flagsToSet or flag
- } else {
- flagsToClear = flagsToClear or flag
+ if (ShadeWindowGoesAround.isEnabled && bitCount(flag) > 1) {
+ error("Flags should be a single bit.")
}
+ val toSet = flagWithOptionalOverrides(flag, enabled, displayId, sceneContainerPlugin)
+ stateChange.setFlag(flag, toSet)
return this
}
@@ -147,27 +146,19 @@ constructor(
ReplaceWith("commitUpdate()"),
)
override fun commitUpdate(displayId: Int) {
- // TODO b/398011576 - handle updates for different displays.
commitUpdate()
}
override fun commitUpdate() {
- updateFlags()
- flagsToSet = 0
- flagsToClear = 0
- }
-
- private fun updateFlags() {
- var newState = flags
- newState = newState or flagsToSet
- newState = newState and flagsToClear.inv()
+ val newState = stateChange.applyTo(flags)
notifyAndSetSystemUiStateChanged(newState, flags)
+ stateChange.clear()
}
/** Notify all those who are registered that the state has changed. */
private fun notifyAndSetSystemUiStateChanged(newFlags: Long, oldFlags: Long) {
if (SysUiState.DEBUG) {
- Log.d(TAG, "SysUiState changed: old=$oldFlags new=$newFlags")
+ Log.d(TAG, "SysUiState changed for displayId=$displayId: old=$oldFlags new=$newFlags")
}
if (newFlags != oldFlags) {
_flags = newFlags
@@ -185,6 +176,8 @@ constructor(
pw.println(QuickStepContract.isBackGestureDisabled(flags, false /* forTrackpad */))
pw.print(" assistantGestureDisabled=")
pw.println(QuickStepContract.isAssistantGestureDisabled(flags))
+ pw.print(" pendingStateChanges=")
+ pw.println(stateChange.toString())
}
override fun destroy() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
index b2764e1a2302..6cebcd98a0ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
@@ -24,6 +24,8 @@ per-file *Blur* = set noparent
per-file *Blur* = shanh@google.com, rahulbanerjee@google.com
# Not setting noparent here, since *Mode* matches many other classes (e.g., *ViewModel*)
per-file *Mode* = file:notification/OWNERS
+per-file *SmartReply* = set noparent
+per-file *SmartReply* = file:notification/OWNERS
per-file *RemoteInput* = set noparent
per-file *RemoteInput* = file:notification/OWNERS
per-file *EmptyShadeView* = set noparent
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt
index 3bd8af690763..6657c428a594 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt
@@ -20,6 +20,7 @@ import android.os.UserHandle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -31,7 +32,14 @@ class UserLockedInteractor
constructor(
@Background val backgroundDispatcher: CoroutineDispatcher,
val userRepository: UserRepository,
+ val selectedUserInteractor: SelectedUserInteractor,
) {
+ /** Whether the current user is unlocked */
+ val currentUserUnlocked: Flow<Boolean> =
+ selectedUserInteractor.selectedUserInfo.flatMapLatestConflated { user ->
+ isUserUnlocked(user.userHandle)
+ }
+
fun isUserUnlocked(userHandle: UserHandle?): Flow<Boolean> =
userRepository.isUserUnlocked(userHandle).flowOn(backgroundDispatcher)
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/SettingsForUserRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/SettingsForUserRepository.kt
index 94b3fd244a92..6cdc94251d21 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/SettingsForUserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/SettingsForUserRepository.kt
@@ -47,6 +47,11 @@ abstract class SettingsForUserRepository(
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
+ fun intSettingForUser(userId: Int, name: String, defaultValue: Int = 0): Flow<Int> =
+ settingObserver(name, userId) { userSettings.getIntForUser(name, defaultValue, userId) }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+
fun <T> settingObserver(name: String, userId: Int, settingsReader: () -> T): Flow<T> {
return userSettings
.observerFlow(userId, name)
@@ -63,4 +68,14 @@ abstract class SettingsForUserRepository(
userSettings.getBoolForUser(name, defaultValue, userId)
}
}
+
+ suspend fun setIntForUser(userId: Int, name: String, value: Int) {
+ withContext(backgroundContext) { userSettings.putIntForUser(name, value, userId) }
+ }
+
+ suspend fun getIntForUser(userId: Int, name: String, defaultValue: Int = 0): Int {
+ return withContext(backgroundContext) {
+ userSettings.getIntForUser(name, defaultValue, userId)
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 845be0252581..60345a358bac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -24,12 +24,15 @@ import android.widget.LinearLayout
import android.window.RemoteTransition
import android.window.TransitionFilter
import android.window.WindowAnimationState
+import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.EmptyTestActivity
+import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
-import com.android.systemui.shared.Flags
+import com.android.systemui.shared.Flags as SharedFlags
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.wm.shell.shared.ShellTransitions
@@ -43,7 +46,6 @@ import kotlin.concurrent.thread
import kotlin.test.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
-import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
@@ -57,8 +59,8 @@ import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
-import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.spy
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -66,21 +68,23 @@ import org.mockito.junit.MockitoJUnit
@RunWithLooper
class ActivityTransitionAnimatorTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val transitionContainer = LinearLayout(mContext)
+
private val mainExecutor = context.mainExecutor
private val testTransitionAnimator = fakeTransitionAnimator(mainExecutor)
private val testShellTransitions = FakeShellTransitions()
+
+ private val Kosmos.underTest by Kosmos.Fixture { activityTransitionAnimator }
+
@Mock lateinit var callback: ActivityTransitionAnimator.Callback
@Mock lateinit var listener: ActivityTransitionAnimator.Listener
- @Spy private val controller = TestTransitionAnimatorController(transitionContainer)
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
- private lateinit var underTest: ActivityTransitionAnimator
- @get:Rule val rule = MockitoJUnit.rule()
+ @get:Rule(order = 0) val mockitoRule = MockitoJUnit.rule()
+ @get:Rule(order = 1) val activityRule = ActivityScenarioRule(EmptyTestActivity::class.java)
@Before
fun setup() {
- underTest =
+ kosmos.activityTransitionAnimator =
ActivityTransitionAnimator(
mainExecutor,
ActivityTransitionAnimator.TransitionRegister.fromShellTransitions(
@@ -89,19 +93,20 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
testTransitionAnimator,
testTransitionAnimator,
disableWmTimeout = true,
+ skipReparentTransaction = true,
)
- underTest.callback = callback
- underTest.addListener(listener)
+ kosmos.activityTransitionAnimator.callback = callback
+ kosmos.activityTransitionAnimator.addListener(listener)
}
@After
fun tearDown() {
- underTest.removeListener(listener)
+ kosmos.activityTransitionAnimator.removeListener(listener)
}
private fun startIntentWithAnimation(
- animator: ActivityTransitionAnimator = underTest,
- controller: ActivityTransitionAnimator.Controller? = this.controller,
+ controller: ActivityTransitionAnimator.Controller?,
+ animator: ActivityTransitionAnimator = kosmos.activityTransitionAnimator,
animate: Boolean = true,
intentStarter: (RemoteAnimationAdapter?) -> Int,
) {
@@ -119,129 +124,152 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@Test
fun animationAdapterIsNullIfControllerIsNull() {
- var startedIntent = false
- var animationAdapter: RemoteAnimationAdapter? = null
+ kosmos.runTest {
+ var startedIntent = false
+ var animationAdapter: RemoteAnimationAdapter? = null
- startIntentWithAnimation(controller = null) { adapter ->
- startedIntent = true
- animationAdapter = adapter
+ startIntentWithAnimation(controller = null) { adapter ->
+ startedIntent = true
+ animationAdapter = adapter
- ActivityManager.START_SUCCESS
- }
+ ActivityManager.START_SUCCESS
+ }
- assertTrue(startedIntent)
- assertNull(animationAdapter)
+ assertTrue(startedIntent)
+ assertNull(animationAdapter)
+ }
}
@Test
fun animatesIfActivityOpens() {
- val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
- var animationAdapter: RemoteAnimationAdapter? = null
- startIntentWithAnimation { adapter ->
- animationAdapter = adapter
- ActivityManager.START_SUCCESS
- }
+ kosmos.runTest {
+ val controller = createController()
+ val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ var animationAdapter: RemoteAnimationAdapter? = null
+ startIntentWithAnimation(controller) { adapter ->
+ animationAdapter = adapter
+ ActivityManager.START_SUCCESS
+ }
- assertNotNull(animationAdapter)
- waitForIdleSync()
- verify(controller).onIntentStarted(willAnimateCaptor.capture())
- assertTrue(willAnimateCaptor.value)
+ assertNotNull(animationAdapter)
+ waitForIdleSync()
+ verify(controller).onIntentStarted(willAnimateCaptor.capture())
+ assertTrue(willAnimateCaptor.value)
+ }
}
@Test
fun doesNotAnimateIfActivityIsAlreadyOpen() {
- val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
- startIntentWithAnimation { ActivityManager.START_DELIVERED_TO_TOP }
+ kosmos.runTest {
+ val controller = createController()
+ val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ startIntentWithAnimation(controller) { ActivityManager.START_DELIVERED_TO_TOP }
- waitForIdleSync()
- verify(controller).onIntentStarted(willAnimateCaptor.capture())
- assertFalse(willAnimateCaptor.value)
+ waitForIdleSync()
+ verify(controller).onIntentStarted(willAnimateCaptor.capture())
+ assertFalse(willAnimateCaptor.value)
+ }
}
@Test
fun animatesIfActivityIsAlreadyOpenAndIsOnKeyguard() {
- `when`(callback.isOnKeyguard()).thenReturn(true)
+ kosmos.runTest {
+ `when`(callback.isOnKeyguard()).thenReturn(true)
- val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
- var animationAdapter: RemoteAnimationAdapter? = null
+ val controller = createController()
+ val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ var animationAdapter: RemoteAnimationAdapter? = null
- startIntentWithAnimation(underTest) { adapter ->
- animationAdapter = adapter
- ActivityManager.START_DELIVERED_TO_TOP
- }
+ startIntentWithAnimation(controller, underTest) { adapter ->
+ animationAdapter = adapter
+ ActivityManager.START_DELIVERED_TO_TOP
+ }
- waitForIdleSync()
- verify(controller).onIntentStarted(willAnimateCaptor.capture())
- verify(callback).hideKeyguardWithAnimation(any())
+ waitForIdleSync()
+ verify(controller).onIntentStarted(willAnimateCaptor.capture())
+ verify(callback).hideKeyguardWithAnimation(any())
- assertTrue(willAnimateCaptor.value)
- assertNull(animationAdapter)
+ assertTrue(willAnimateCaptor.value)
+ assertNull(animationAdapter)
+ }
}
@Test
fun doesNotAnimateIfAnimateIsFalse() {
- val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
- startIntentWithAnimation(animate = false) { ActivityManager.START_SUCCESS }
+ kosmos.runTest {
+ val controller = createController()
+ val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ startIntentWithAnimation(controller, animate = false) { ActivityManager.START_SUCCESS }
- waitForIdleSync()
- verify(controller).onIntentStarted(willAnimateCaptor.capture())
- assertFalse(willAnimateCaptor.value)
+ waitForIdleSync()
+ verify(controller).onIntentStarted(willAnimateCaptor.capture())
+ assertFalse(willAnimateCaptor.value)
+ }
}
- @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
+ @EnableFlags(SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
@Test
fun registersReturnIffCookieIsPresent() {
- `when`(callback.isOnKeyguard()).thenReturn(false)
+ kosmos.runTest {
+ `when`(callback.isOnKeyguard()).thenReturn(false)
- startIntentWithAnimation(underTest, controller) { ActivityManager.START_DELIVERED_TO_TOP }
+ val controller = createController()
+ startIntentWithAnimation(controller, underTest) {
+ ActivityManager.START_DELIVERED_TO_TOP
+ }
- waitForIdleSync()
- assertTrue(testShellTransitions.remotes.isEmpty())
- assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
+ waitForIdleSync()
+ assertTrue(testShellTransitions.remotes.isEmpty())
+ assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
- val controller =
- object : DelegateTransitionAnimatorController(controller) {
- override val transitionCookie
- get() = ActivityTransitionAnimator.TransitionCookie("testCookie")
- }
+ val controllerWithCookie =
+ object : DelegateTransitionAnimatorController(controller) {
+ override val transitionCookie
+ get() = ActivityTransitionAnimator.TransitionCookie("testCookie")
+ }
- startIntentWithAnimation(underTest, controller) { ActivityManager.START_DELIVERED_TO_TOP }
+ startIntentWithAnimation(controllerWithCookie, underTest) {
+ ActivityManager.START_DELIVERED_TO_TOP
+ }
- waitForIdleSync()
- assertEquals(1, testShellTransitions.remotes.size)
- assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
+ waitForIdleSync()
+ assertEquals(1, testShellTransitions.remotes.size)
+ assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
+ }
}
@EnableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun registersLongLivedTransition() {
kosmos.runTest {
- var factory = controllerFactory()
+ val controller = createController()
+ var factory = controllerFactory(controller)
underTest.register(factory.cookie, factory, testScope)
assertEquals(2, testShellTransitions.remotes.size)
- factory = controllerFactory()
+ factory = controllerFactory(controller)
underTest.register(factory.cookie, factory, testScope)
assertEquals(4, testShellTransitions.remotes.size)
}
}
@EnableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun registersLongLivedTransitionOverridingPreviousRegistration() {
kosmos.runTest {
+ val controller = createController()
val cookie = ActivityTransitionAnimator.TransitionCookie("test_cookie")
- var factory = controllerFactory(cookie)
+ var factory = controllerFactory(controller, cookie)
underTest.register(cookie, factory, testScope)
val transitions = testShellTransitions.remotes.values.toList()
- factory = controllerFactory(cookie)
+ factory = controllerFactory(controller, cookie)
underTest.register(cookie, factory, testScope)
assertEquals(2, testShellTransitions.remotes.size)
for (transition in transitions) {
@@ -250,23 +278,25 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
}
}
- @DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
+ @DisableFlags(SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
@Test
fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() {
kosmos.runTest {
- val factory = controllerFactory(component = null)
+ val factory = controllerFactory(createController(), component = null)
assertThrows(IllegalStateException::class.java) {
underTest.register(factory.cookie, factory, testScope)
}
}
}
- @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
+ @EnableFlags(SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
@Test
fun doesNotRegisterLongLivedTransitionIfMissingRequiredProperties() {
kosmos.runTest {
+ val controller = createController()
+
// No ComponentName
- var factory = controllerFactory(component = null)
+ var factory = controllerFactory(controller, component = null)
assertThrows(IllegalStateException::class.java) {
underTest.register(factory.cookie, factory, testScope)
}
@@ -280,7 +310,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
testTransitionAnimator,
disableWmTimeout = true,
)
- factory = controllerFactory()
+ factory = controllerFactory(controller)
assertThrows(IllegalStateException::class.java) {
activityTransitionAnimator.register(factory.cookie, factory, testScope)
}
@@ -288,17 +318,18 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
}
@EnableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun unregistersLongLivedTransition() {
kosmos.runTest {
+ val controller = createController()
val cookies = arrayOfNulls<ActivityTransitionAnimator.TransitionCookie>(3)
for (index in 0 until 3) {
cookies[index] = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
- val factory = controllerFactory(cookies[index]!!)
+ val factory = controllerFactory(controller, cookies[index]!!)
underTest.register(factory.cookie, factory, testScope)
}
@@ -315,75 +346,98 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@Test
fun doesNotStartIfAnimationIsCancelled() {
- val runner = underTest.createEphemeralRunner(controller)
- runner.onAnimationCancelled()
- runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback)
+ kosmos.runTest {
+ val controller = createController()
+ val runner = underTest.createEphemeralRunner(controller)
+ runner.onAnimationCancelled()
+ runner.onAnimationStart(
+ TRANSIT_NONE,
+ emptyArray(),
+ emptyArray(),
+ emptyArray(),
+ iCallback,
+ )
- waitForIdleSync()
- verify(controller).onTransitionAnimationCancelled()
- verify(controller, never()).onTransitionAnimationStart(anyBoolean())
- verify(listener).onTransitionAnimationCancelled()
- verify(listener, never()).onTransitionAnimationStart()
- assertNull(runner.delegate)
+ waitForIdleSync()
+ verify(controller).onTransitionAnimationCancelled()
+ verify(controller, never()).onTransitionAnimationStart(anyBoolean())
+ verify(listener).onTransitionAnimationCancelled()
+ verify(listener, never()).onTransitionAnimationStart()
+ assertNull(runner.delegate)
+ }
}
@Test
fun cancelsIfNoOpeningWindowIsFound() {
- val runner = underTest.createEphemeralRunner(controller)
- runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback)
+ kosmos.runTest {
+ val controller = createController()
+ val runner = underTest.createEphemeralRunner(controller)
+ runner.onAnimationStart(
+ TRANSIT_NONE,
+ emptyArray(),
+ emptyArray(),
+ emptyArray(),
+ iCallback,
+ )
- waitForIdleSync()
- verify(controller).onTransitionAnimationCancelled()
- verify(controller, never()).onTransitionAnimationStart(anyBoolean())
- verify(listener).onTransitionAnimationCancelled()
- verify(listener, never()).onTransitionAnimationStart()
- assertNull(runner.delegate)
+ waitForIdleSync()
+ verify(controller).onTransitionAnimationCancelled()
+ verify(controller, never()).onTransitionAnimationStart(anyBoolean())
+ verify(listener).onTransitionAnimationCancelled()
+ verify(listener, never()).onTransitionAnimationStart()
+ assertNull(runner.delegate)
+ }
}
@Test
fun startsAnimationIfWindowIsOpening() {
- val runner = underTest.createEphemeralRunner(controller)
- runner.onAnimationStart(
- TRANSIT_NONE,
- arrayOf(fakeWindow()),
- emptyArray(),
- emptyArray(),
- iCallback,
- )
- waitForIdleSync()
- verify(listener).onTransitionAnimationStart()
- verify(controller).onTransitionAnimationStart(anyBoolean())
+ kosmos.runTest {
+ val controller = createController()
+ val runner = underTest.createEphemeralRunner(controller)
+ runner.onAnimationStart(
+ TRANSIT_NONE,
+ arrayOf(fakeWindow()),
+ emptyArray(),
+ emptyArray(),
+ iCallback,
+ )
+ waitForIdleSync()
+ verify(listener).onTransitionAnimationStart()
+ verify(controller).onTransitionAnimationStart(anyBoolean())
+ }
}
@Test
fun creatingControllerFromNormalViewThrows() {
- assertThrows(IllegalArgumentException::class.java) {
- ActivityTransitionAnimator.Controller.fromView(FrameLayout(mContext))
+ kosmos.runTest {
+ assertThrows(IllegalArgumentException::class.java) {
+ ActivityTransitionAnimator.Controller.fromView(FrameLayout(mContext))
+ }
}
}
@DisableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun creatingRunnerWithLazyInitializationThrows_whenTheFlagsAreDisabled() {
kosmos.runTest {
assertThrows(IllegalStateException::class.java) {
- val factory = controllerFactory()
+ val factory = controllerFactory(createController())
underTest.createLongLivedRunner(factory, testScope, forLaunch = true)
}
}
}
@EnableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun runnerCreatesDelegateLazily_onAnimationStart() {
kosmos.runTest {
- val factory = controllerFactory()
+ val factory = controllerFactory(createController())
val runner = underTest.createLongLivedRunner(factory, testScope, forLaunch = true)
assertNull(runner.delegate)
@@ -412,13 +466,13 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
}
@EnableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun runnerCreatesDelegateLazily_onAnimationTakeover() {
kosmos.runTest {
- val factory = controllerFactory()
+ val factory = controllerFactory(createController())
val runner = underTest.createLongLivedRunner(factory, testScope, forLaunch = false)
assertNull(runner.delegate)
@@ -446,58 +500,78 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
}
@DisableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun animationTakeoverThrows_whenTheFlagsAreDisabled() {
- val runner = underTest.createEphemeralRunner(controller)
- assertThrows(IllegalStateException::class.java) {
- runner.takeOverAnimation(
- arrayOf(fakeWindow()),
- emptyArray(),
- SurfaceControl.Transaction(),
- iCallback,
- )
+ kosmos.runTest {
+ val controller = createController()
+ val runner = underTest.createEphemeralRunner(controller)
+ assertThrows(IllegalStateException::class.java) {
+ runner.takeOverAnimation(
+ arrayOf(fakeWindow()),
+ emptyArray(),
+ SurfaceControl.Transaction(),
+ iCallback,
+ )
+ }
}
}
@DisableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun disposeRunner_delegateDereferenced() {
- val runner = underTest.createEphemeralRunner(controller)
- assertNotNull(runner.delegate)
- runner.dispose()
- waitForIdleSync()
- assertNull(runner.delegate)
+ kosmos.runTest {
+ val controller = createController()
+ val runner = underTest.createEphemeralRunner(controller)
+ assertNotNull(runner.delegate)
+ runner.dispose()
+ waitForIdleSync()
+ assertNull(runner.delegate)
+ }
}
@Test
fun concurrentListenerModification_doesNotThrow() {
- // Need a second listener to trigger the concurrent modification.
- underTest.addListener(object : ActivityTransitionAnimator.Listener {})
- `when`(listener.onTransitionAnimationStart()).thenAnswer {
- underTest.removeListener(listener)
- listener
- }
+ kosmos.runTest {
+ // Need a second listener to trigger the concurrent modification.
+ underTest.addListener(object : ActivityTransitionAnimator.Listener {})
+ `when`(listener.onTransitionAnimationStart()).thenAnswer {
+ underTest.removeListener(listener)
+ listener
+ }
- val runner = underTest.createEphemeralRunner(controller)
- runner.onAnimationStart(
- TRANSIT_NONE,
- arrayOf(fakeWindow()),
- emptyArray(),
- emptyArray(),
- iCallback,
- )
+ val controller = createController()
+ val runner = underTest.createEphemeralRunner(controller)
+ runner.onAnimationStart(
+ TRANSIT_NONE,
+ arrayOf(fakeWindow()),
+ emptyArray(),
+ emptyArray(),
+ iCallback,
+ )
+
+ waitForIdleSync()
+ verify(listener).onTransitionAnimationStart()
+ }
+ }
+ private fun createController(): TestTransitionAnimatorController {
+ lateinit var transitionContainer: ViewGroup
+ activityRule.scenario.onActivity { activity ->
+ transitionContainer = LinearLayout(activity)
+ activity.setContentView(transitionContainer)
+ }
waitForIdleSync()
- verify(listener).onTransitionAnimationStart()
+ return spy(TestTransitionAnimatorController(transitionContainer))
}
private fun controllerFactory(
+ controller: ActivityTransitionAnimator.Controller,
cookie: ActivityTransitionAnimator.TransitionCookie =
mock(ActivityTransitionAnimator.TransitionCookie::class.java),
component: ComponentName? = mock(ComponentName::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
index 155059ea5ed9..e0118b18ff64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
@@ -244,7 +244,7 @@ class LauncherProxyServiceTest : SysuiTestCase() {
`when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(true)
`when`(userManager.isUserForeground()).thenReturn(true)
val spyContext = spy(context)
- val ops = createLauncherProxyService(spyContext)
+ val ops = assertLogsWtf { createLauncherProxyService(spyContext) }.result
ops.startConnectionToCurrentUser()
verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 51bb38fa5ba9..f72645eb8596 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -51,6 +51,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.assertLogsWtf
import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.res.R
import com.android.systemui.scene.ui.view.WindowRootViewKeyEventHandler
@@ -413,7 +414,9 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
// THEN move is ignored, down is handled, and window is notified
assertThat(interactionEventHandler.handleDispatchTouchEvent(MOVE_EVENT)).isFalse()
- assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue()
+ assertLogsWtf {
+ assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue()
+ }
verify(notificationShadeWindowController).setLaunchingActivity(false)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 4315c0f638ac..84f39be2eeed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.row;
import static android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES;
+import static com.android.systemui.log.LogAssertKt.assertRunnableLogsWtf;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.PKG;
import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.USER_HANDLE;
@@ -1147,7 +1148,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
public void hasStatusBarChipDuringHeadsUpAnimation_flagOff_false() throws Exception {
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- row.setHasStatusBarChipDuringHeadsUpAnimation(true);
+ assertRunnableLogsWtf(() -> row.setHasStatusBarChipDuringHeadsUpAnimation(true));
assertThat(row.hasStatusBarChipDuringHeadsUpAnimation()).isFalse();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java
index 1cadb3c0a909..e1bab8ec47e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java
@@ -46,6 +46,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.notification.Flags;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.log.LogAssertKt;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -173,8 +174,10 @@ public class NotificationCustomContentMemoryVerifierTest extends SysuiTestCase {
public void satisfiesMemoryLimits_viewWithoutCustomNotificationRoot_returnsTrue() {
NotificationEntry entry = new NotificationEntryBuilder().build();
View view = new FrameLayout(mContext);
- assertThat(NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry))
- .isTrue();
+ LogAssertKt.assertRunnableLogsWtf(() -> {
+ assertThat(NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry))
+ .isTrue();
+ });
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 8de931a7af40..0d7ce5353cd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -2055,6 +2055,9 @@ public class BubblesTest extends SysuiTestCase {
@Test
public void testShowStackEdu_isConversationBubble() {
+ // TODO(b/401025577): Prevent this test from raising a WTF, and remove this exemption
+ mLogWtfRule.addFailureLogExemption(log-> log.getTag().equals("FloatingCoordinator"));
+
// Setup
setPrefBoolean(StackEducationView.PREF_STACK_EDUCATION, false);
BubbleEntry bubbleEntry = createBubbleEntry();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 252c70a61b86..e550e88b7bc7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -46,6 +46,7 @@ import androidx.test.uiautomator.UiDevice;
import com.android.internal.protolog.ProtoLog;
import com.android.systemui.broadcast.FakeBroadcastDispatcher;
import com.android.systemui.flags.SceneContainerRule;
+import com.android.systemui.log.LogWtfHandlerRule;
import org.junit.After;
import org.junit.AfterClass;
@@ -127,6 +128,8 @@ public abstract class SysuiTestCase {
@Rule public final SetFlagsRule mSetFlagsRule =
isRobolectricTest() ? new SetFlagsRule() : mSetFlagsClassRule.createSetFlagsRule();
+ @Rule public final LogWtfHandlerRule mLogWtfRule = new LogWtfHandlerRule();
+
@Rule(order = 10)
public final SceneContainerRule mSceneContainerRule = new SceneContainerRule();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryKosmos.kt
new file mode 100644
index 000000000000..130c2987912e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 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.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.carProjectionRepository by
+ Kosmos.Fixture<CarProjectionRepository> { FakeCarProjectionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCarProjectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCarProjectionRepository.kt
new file mode 100644
index 000000000000..4042342923ea
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCarProjectionRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2025 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.communal.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeCarProjectionRepository : CarProjectionRepository {
+ private val _projectionActive = MutableStateFlow(false)
+ override val projectionActive: Flow<Boolean> = _projectionActive.asStateFlow()
+
+ override suspend fun isProjectionActive(): Boolean {
+ return _projectionActive.value
+ }
+
+ fun setProjectionActive(active: Boolean) {
+ _projectionActive.value = active
+ }
+}
+
+val CarProjectionRepository.fake
+ get() = this as FakeCarProjectionRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorKosmos.kt
new file mode 100644
index 000000000000..23bbe36203e6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2025 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.communal.domain.interactor
+
+import com.android.systemui.communal.data.repository.carProjectionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.carProjectionInteractor by Fixture { CarProjectionInteractor(carProjectionRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorKosmos.kt
new file mode 100644
index 000000000000..5735cf82cca0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 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.communal.domain.interactor
+
+import com.android.systemui.common.domain.interactor.batteryInteractor
+import com.android.systemui.communal.posturing.domain.interactor.posturingInteractor
+import com.android.systemui.dock.dockManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.backgroundCoroutineContext
+
+val Kosmos.communalAutoOpenInteractor by Fixture {
+ CommunalAutoOpenInteractor(
+ communalSettingsInteractor = communalSettingsInteractor,
+ backgroundContext = backgroundCoroutineContext,
+ batteryInteractor = batteryInteractor,
+ posturingInteractor = posturingInteractor,
+ dockManager = dockManager,
+ allowSwipeAlways = false,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index b8b2ec5a58ae..316fcbb85b26 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -16,17 +16,14 @@
package com.android.systemui.communal.domain.interactor
-import android.content.pm.UserInfo
import android.content.testableContext
import android.os.userManager
import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.common.domain.interactor.batteryInteractor
+import com.android.systemui.communal.data.model.SuppressionReason
import com.android.systemui.communal.data.repository.communalMediaRepository
import com.android.systemui.communal.data.repository.communalSmartspaceRepository
import com.android.systemui.communal.data.repository.communalWidgetRepository
-import com.android.systemui.communal.posturing.domain.interactor.posturingInteractor
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
-import com.android.systemui.dock.dockManager
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -39,13 +36,9 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.activityStarter
-import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.settings.userTracker
import com.android.systemui.statusbar.phone.fakeManagedProfileController
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.user.domain.interactor.userLockedInteractor
import com.android.systemui.util.mockito.mock
val Kosmos.communalInteractor by Fixture {
@@ -70,10 +63,6 @@ val Kosmos.communalInteractor by Fixture {
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
managedProfileController = fakeManagedProfileController,
- batteryInteractor = batteryInteractor,
- dockManager = dockManager,
- posturingInteractor = posturingInteractor,
- userLockedInteractor = userLockedInteractor,
)
}
@@ -86,28 +75,28 @@ fun Kosmos.setCommunalV2ConfigEnabled(enabled: Boolean) {
)
}
-suspend fun Kosmos.setCommunalEnabled(enabled: Boolean): UserInfo {
+fun Kosmos.setCommunalEnabled(enabled: Boolean) {
fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, enabled)
- return if (enabled) {
- fakeUserRepository.asMainUser()
- } else {
- fakeUserRepository.asDefaultUser()
- }
+ val suppressionReasons =
+ if (enabled) {
+ emptyList()
+ } else {
+ listOf(SuppressionReason.ReasonUnknown())
+ }
+ communalSettingsInteractor.setSuppressionReasons(suppressionReasons)
}
-suspend fun Kosmos.setCommunalV2Enabled(enabled: Boolean): UserInfo {
+fun Kosmos.setCommunalV2Enabled(enabled: Boolean) {
setCommunalV2ConfigEnabled(enabled)
return setCommunalEnabled(enabled)
}
-suspend fun Kosmos.setCommunalAvailable(available: Boolean): UserInfo {
- val user = setCommunalEnabled(available)
+fun Kosmos.setCommunalAvailable(available: Boolean) {
+ setCommunalEnabled(available)
fakeKeyguardRepository.setKeyguardShowing(available)
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, available)
- return user
}
-suspend fun Kosmos.setCommunalV2Available(available: Boolean): UserInfo {
+fun Kosmos.setCommunalV2Available(available: Boolean) {
setCommunalV2ConfigEnabled(available)
- return setCommunalAvailable(available)
+ setCommunalAvailable(available)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
index fb983f7c605f..d2fbb515e686 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
@@ -24,7 +24,6 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.settings.userTracker
import com.android.systemui.user.domain.interactor.selectedUserInteractor
-import com.android.systemui.util.mockito.mock
val Kosmos.communalSettingsInteractor by Fixture {
CommunalSettingsInteractor(
@@ -34,6 +33,5 @@ val Kosmos.communalSettingsInteractor by Fixture {
repository = communalSettingsRepository,
userInteractor = selectedUserInteractor,
userTracker = userTracker,
- tableLogBuffer = mock(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java
deleted file mode 100644
index b99310bcbe38..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dock;
-
-/**
- * A rudimentary fake for DockManager.
- */
-public class DockManagerFake implements DockManager {
- DockEventListener mCallback;
- AlignmentStateListener mAlignmentListener;
- private boolean mDocked;
-
- @Override
- public void addListener(DockEventListener callback) {
- this.mCallback = callback;
- }
-
- @Override
- public void removeListener(DockEventListener callback) {
- this.mCallback = null;
- }
-
- @Override
- public void addAlignmentStateListener(AlignmentStateListener listener) {
- mAlignmentListener = listener;
- }
-
- @Override
- public void removeAlignmentStateListener(AlignmentStateListener listener) {
- mAlignmentListener = listener;
- }
-
- @Override
- public boolean isDocked() {
- return mDocked;
- }
-
- /** Sets the docked state */
- public void setIsDocked(boolean docked) {
- mDocked = docked;
- }
-
- @Override
- public boolean isHidden() {
- return false;
- }
-
- /** Notifies callbacks of dock state change */
- public void setDockEvent(int event) {
- mCallback.onEvent(event);
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.kt
new file mode 100644
index 000000000000..6a43c40612a7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2025 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.dock
+
+import com.android.systemui.dock.DockManager.AlignmentStateListener
+
+/** A rudimentary fake for DockManager. */
+class DockManagerFake : DockManager {
+ private val callbacks = mutableSetOf<DockManager.DockEventListener>()
+ private val alignmentListeners = mutableSetOf<AlignmentStateListener>()
+ private var docked = false
+
+ override fun addListener(callback: DockManager.DockEventListener) {
+ callbacks.add(callback)
+ }
+
+ override fun removeListener(callback: DockManager.DockEventListener) {
+ callbacks.remove(callback)
+ }
+
+ override fun addAlignmentStateListener(listener: AlignmentStateListener) {
+ alignmentListeners.add(listener)
+ }
+
+ override fun removeAlignmentStateListener(listener: AlignmentStateListener) {
+ alignmentListeners.remove(listener)
+ }
+
+ override fun isDocked(): Boolean {
+ return docked
+ }
+
+ /** Sets the docked state */
+ fun setIsDocked(docked: Boolean) {
+ this.docked = docked
+ }
+
+ override fun isHidden(): Boolean {
+ return false
+ }
+
+ /** Notifies callbacks of dock state change */
+ fun setDockEvent(event: Int) {
+ for (callback in callbacks) {
+ callback.onEvent(event)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index bdfa875f5429..9b0a9830dcc4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard.domain.interactor
-import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
@@ -43,6 +42,5 @@ val Kosmos.fromAodTransitionInteractor by
wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
communalSettingsInteractor = communalSettingsInteractor,
communalSceneInteractor = communalSceneInteractor,
- communalInteractor = communalInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index 985044c80f18..511bede7349b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard.domain.interactor
-import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
@@ -42,7 +41,6 @@ var Kosmos.fromLockscreenTransitionInteractor by
communalSettingsInteractor = communalSettingsInteractor,
swipeToDismissInteractor = swipeToDismissInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
- communalInteractor = communalInteractor,
communalSceneInteractor = communalSceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
index b41ceff5f581..a42f2025cdaa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
@@ -18,7 +18,6 @@ package com.android.systemui.log
import android.util.Log
import android.util.Log.TerribleFailureHandler
import com.google.common.truth.Truth.assertWithMessage
-import java.util.concurrent.Callable
/** Asserts that [notLoggingBlock] does not make a call to [Log.wtf] */
fun <T> assertDoesNotLogWtf(
@@ -65,15 +64,6 @@ fun <T> assertLogsWtf(
return WtfBlockResult(caught, result)
}
-/** Assert that [loggingCallable] makes a call to [Log.wtf] */
-@JvmOverloads
-fun <T> assertLogsWtf(
- message: String = "Expected Log.wtf to be called",
- allowMultiple: Boolean = false,
- loggingCallable: Callable<T>,
-): WtfBlockResult<T> =
- assertLogsWtf(message = message, allowMultiple = allowMultiple, loggingCallable::call)
-
/** Assert that [loggingBlock] makes at least one call to [Log.wtf] */
@JvmOverloads
fun <T> assertLogsWtfs(
@@ -81,13 +71,6 @@ fun <T> assertLogsWtfs(
loggingBlock: () -> T,
): WtfBlockResult<T> = assertLogsWtf(message, allowMultiple = true, loggingBlock)
-/** Assert that [loggingCallable] makes at least one call to [Log.wtf] */
-@JvmOverloads
-fun <T> assertLogsWtfs(
- message: String = "Expected Log.wtf to be called once or more",
- loggingCallable: Callable<T>,
-): WtfBlockResult<T> = assertLogsWtf(message, allowMultiple = true, loggingCallable)
-
/** The data passed to [TerribleFailureHandler.onTerribleFailure] */
data class TerribleFailureLog(
val tag: String,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt
index 933c351679a4..6bb908a6ef07 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt
@@ -22,5 +22,9 @@ import com.android.systemui.user.data.repository.userRepository
val Kosmos.userLockedInteractor by
Kosmos.Fixture {
- UserLockedInteractor(backgroundDispatcher = testDispatcher, userRepository = userRepository)
+ UserLockedInteractor(
+ backgroundDispatcher = testDispatcher,
+ userRepository = userRepository,
+ selectedUserInteractor = selectedUserInteractor,
+ )
}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
index 02987a98417f..15f186b047f2 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
@@ -132,8 +132,8 @@ final class IInputMethodManagerImpl extends IInputMethodManager.Stub {
@Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
- boolean useAsyncShowHideMethod);
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible,
+ int startInputSeq, boolean useAsyncShowHideMethod);
InputBindResult startInputOrWindowGainedFocus(
@StartInputReason int startInputReason, IInputMethodClient client,
@@ -142,7 +142,7 @@ final class IInputMethodManagerImpl extends IInputMethodManager.Stub {
@Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher);
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible);
void showInputMethodPickerFromClient(IInputMethodClient client, int auxiliarySubtypeMode);
@@ -324,11 +324,11 @@ final class IInputMethodManagerImpl extends IInputMethodManager.Stub {
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible) {
return mCallback.startInputOrWindowGainedFocus(
startInputReason, client, windowToken, startInputFlags, softInputMode,
windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, userId, imeDispatcher);
+ unverifiedTargetSdkVersion, userId, imeDispatcher, imeRequestedVisible);
}
@Override
@@ -340,13 +340,13 @@ final class IInputMethodManagerImpl extends IInputMethodManager.Stub {
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
- boolean useAsyncShowHideMethod) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible,
+ int startInputSeq, boolean useAsyncShowHideMethod) {
mCallback.startInputOrWindowGainedFocusAsync(
startInputReason, client, windowToken, startInputFlags, softInputMode,
windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, userId, imeDispatcher, startInputSeq,
- useAsyncShowHideMethod);
+ unverifiedTargetSdkVersion, userId, imeDispatcher, imeRequestedVisible,
+ startInputSeq, useAsyncShowHideMethod);
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 69353becc692..2c07a3179344 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -443,7 +443,8 @@ public final class ImeVisibilityStateComputer {
}
@GuardedBy("ImfLock.class")
- ImeVisibilityResult computeState(ImeTargetWindowState state, boolean allowVisible) {
+ ImeVisibilityResult computeState(ImeTargetWindowState state, boolean allowVisible,
+ boolean imeRequestedVisible) {
// TODO: Output the request IME visibility state according to the requested window state
final int softInputVisibility = state.mSoftInputModeState & SOFT_INPUT_MASK_STATE;
// Should we auto-show the IME even if the caller has not
@@ -576,7 +577,8 @@ public final class ImeVisibilityStateComputer {
SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
}
}
- if (!state.hasEditorFocused() && mInputShown && state.isStartInputByGainFocus()
+ if (!state.hasEditorFocused() && (mInputShown || (Flags.refactorInsetsController()
+ && imeRequestedVisible)) && state.isStartInputByGainFocus()
&& mService.mInputMethodDeviceConfigs.shouldHideImeWhenNoEditorFocus()) {
// Hide the soft-keyboard when the system do nothing for softInputModeState
// of the window being gained focus without an editor. This behavior benefits
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 68ad8f7e9433..23757757e336 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3725,8 +3725,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
- boolean useAsyncShowHideMethod) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible,
+ int startInputSeq, boolean useAsyncShowHideMethod) {
// implemented by ZeroJankProxy
}
@@ -3739,7 +3739,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible) {
if (UserHandle.getCallingUserId() != userId) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
@@ -3870,7 +3870,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags,
editorInfo, inputConnection, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, bindingController, imeDispatcher, cs);
+ unverifiedTargetSdkVersion, bindingController, imeDispatcher, cs,
+ imeRequestedVisible);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3899,7 +3900,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
IRemoteInputConnection inputContext,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @NonNull InputMethodBindingController bindingController,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs,
+ boolean imeRequestedVisible) {
ProtoLog.v(IMMS_DEBUG, "startInputOrWindowGainedFocusInternalLocked: reason=%s"
+ " client=%s"
+ " inputContext=%s"
@@ -3910,12 +3912,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
+ " unverifiedTargetSdkVersion=%s"
+ " bindingController=%s"
+ " imeDispatcher=%s"
- + " cs=%s",
+ + " cs=%s"
+ + " imeRequestedVisible=%s",
InputMethodDebug.startInputReasonToString(startInputReason), client.asBinder(),
inputContext, editorInfo, InputMethodDebug.startInputFlagsToString(startInputFlags),
InputMethodDebug.softInputModeToString(softInputMode),
Integer.toHexString(windowFlags), unverifiedTargetSdkVersion, bindingController,
- imeDispatcher, cs);
+ imeDispatcher, cs, imeRequestedVisible);
final int userId = bindingController.getUserId();
final var userData = getUserData(userId);
@@ -3963,7 +3966,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
InputBindResult res = null;
final ImeVisibilityResult imeVisRes = visibilityStateComputer.computeState(windowState,
- isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags));
+ isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags),
+ imeRequestedVisible);
if (imeVisRes != null) {
boolean isShow = false;
switch (imeVisRes.getReason()) {
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 72529254545e..12c1d9cbb2a1 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -234,15 +234,15 @@ final class ZeroJankProxy implements IInputMethodManagerImpl.Callback {
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
- boolean useAsyncShowHideMethod) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible,
+ int startInputSeq, boolean useAsyncShowHideMethod) {
offload(() -> {
InputBindResult result = mInner.startInputOrWindowGainedFocus(startInputReason, client,
windowToken, startInputFlags, softInputMode, windowFlags,
editorInfo,
inputConnection, remoteAccessibilityInputConnection,
unverifiedTargetSdkVersion,
- userId, imeDispatcher);
+ userId, imeDispatcher, imeRequestedVisible);
sendOnStartInputResult(client, result, startInputSeq);
// For first-time client bind, MSG_BIND should arrive after MSG_START_INPUT_RESULT.
if (result.result == InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION) {
@@ -269,7 +269,7 @@ final class ZeroJankProxy implements IInputMethodManagerImpl.Callback {
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible) {
// Should never be called when flag is enabled i.e. when this proxy is used.
return null;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 353ccd5836c8..42b63d125d6b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -7104,6 +7104,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
public void setAnimatingTypes(@InsetsType int animatingTypes) {
if (mAnimatingTypes != animatingTypes) {
mAnimatingTypes = animatingTypes;
+
+ if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
+ getInsetsStateController().onAnimatingTypesChanged(this);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 1c90f40085db..040bbe46c3aa 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -332,8 +332,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
if (changed) {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
- invokeOnImeRequestedChangedListener(mDisplayContent.getImeInputTarget(),
- statsToken);
+ invokeOnImeRequestedChangedListener(controlTarget, statsToken);
} else {
// TODO(b/353463205) check cancelled / failed
ImeTracker.forLogging().onCancelled(statsToken,
@@ -387,7 +386,8 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
// not all virtual displays have an ImeInsetsSourceProvider, so it is not
// guaranteed that the IME will be started when the control target reports its
// requested visibility back. Thus, invoking the listener here.
- invokeOnImeRequestedChangedListener(imeInsetsTarget, statsToken);
+ invokeOnImeRequestedChangedListener((InsetsControlTarget) imeInsetsTarget,
+ statsToken);
} else {
ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
@@ -396,18 +396,21 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
}
// TODO(b/353463205) check callers to see if we can make statsToken @NonNull
- private void invokeOnImeRequestedChangedListener(InsetsTarget insetsTarget,
+ private void invokeOnImeRequestedChangedListener(InsetsControlTarget controlTarget,
@Nullable ImeTracker.Token statsToken) {
final var imeListener = mDisplayContent.mWmService.mOnImeRequestedChangedListener;
if (imeListener != null) {
- if (insetsTarget != null) {
+ if (controlTarget != null) {
+ final boolean imeAnimating = Flags.reportAnimatingInsetsTypes()
+ && (controlTarget.getAnimatingTypes() & WindowInsets.Type.ime()) != 0;
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_POSTING_CHANGED_IME_VISIBILITY);
mDisplayContent.mWmService.mH.post(() -> {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_INVOKING_IME_REQUESTED_LISTENER);
- imeListener.onImeRequestedChanged(insetsTarget.getWindowToken(),
- insetsTarget.isRequestedVisible(WindowInsets.Type.ime()), statsToken);
+ imeListener.onImeRequestedChanged(controlTarget.getWindowToken(),
+ controlTarget.isRequestedVisible(WindowInsets.Type.ime())
+ || imeAnimating, statsToken);
});
} else {
ImeTracker.forLogging().onFailed(statsToken,
@@ -420,6 +423,21 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
}
}
+ @Override
+ void onAnimatingTypesChanged(InsetsControlTarget caller) {
+ if (Flags.reportAnimatingInsetsTypes()) {
+ final InsetsControlTarget controlTarget = getControlTarget();
+ // If the IME is not being requested anymore and the animation is finished, we need to
+ // invoke the listener, to let IMS eventually know
+ if (caller != null && caller == controlTarget && !caller.isRequestedVisible(
+ WindowInsets.Type.ime())
+ && (caller.getAnimatingTypes() & WindowInsets.Type.ime()) == 0) {
+ // TODO(b/353463205) check statsToken
+ invokeOnImeRequestedChangedListener(caller, null);
+ }
+ }
+ }
+
private void reportImeDrawnForOrganizerIfNeeded(@NonNull InsetsControlTarget caller) {
final WindowState callerWindow = caller.getWindow();
if (callerWindow == null) {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index b7489029768a..1b693fc05b21 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -673,6 +673,9 @@ class InsetsSourceProvider {
mServerVisible, mClientVisible);
}
+ void onAnimatingTypesChanged(InsetsControlTarget caller) {
+ }
+
protected boolean isLeashReadyForDispatching() {
return isLeashInitialized();
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 5e0395f70e65..810e48f492e1 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -393,6 +393,13 @@ class InsetsStateController {
}
}
+ void onAnimatingTypesChanged(InsetsControlTarget target) {
+ for (int i = mProviders.size() - 1; i >= 0; i--) {
+ final InsetsSourceProvider provider = mProviders.valueAt(i);
+ provider.onAnimatingTypesChanged(target);
+ }
+ }
+
private void notifyPendingInsetsControlChanged() {
if (mPendingTargetProvidersMap.isEmpty()) {
return;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 1022d18ac0e9..ce91fc5baba1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -862,6 +862,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mWmService.scheduleAnimationLocked();
mAnimatingTypes = animatingTypes;
+
+ if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
+ final InsetsStateController insetsStateController =
+ getDisplayContent().getInsetsStateController();
+ insetsStateController.onAnimatingTypesChanged(this);
+ }
}
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 05615f68427d..2339a940e2d0 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -287,6 +287,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe
mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */,
mTargetSdkVersion /* unverifiedTargetSdkVersion */,
mUserId /* userId */,
- mMockImeOnBackInvokedDispatcher /* imeDispatcher */);
+ mMockImeOnBackInvokedDispatcher /* imeDispatcher */,
+ true /* imeRequestedVisible */);
}
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index 70eeae648dd0..aa779197f301 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -267,7 +267,8 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes
// visibility state will be preserved to the current window state.
final ImeTargetWindowState stateWithUnChangedFlag = initImeTargetWindowState(
mWindowToken);
- mComputer.computeState(stateWithUnChangedFlag, true /* allowVisible */);
+ mComputer.computeState(stateWithUnChangedFlag, true /* allowVisible */,
+ true /* imeRequestedVisible */);
assertThat(stateWithUnChangedFlag.isRequestedImeVisible()).isEqualTo(
lastState.isRequestedImeVisible());
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
index 11abc9469c82..b81b570389da 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
@@ -320,7 +320,8 @@ public class InputMethodManagerServiceWindowGainedFocusTest
mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */,
mTargetSdkVersion /* unverifiedTargetSdkVersion */,
mUserId /* userId */,
- mMockImeOnBackInvokedDispatcher /* imeDispatcher */);
+ mMockImeOnBackInvokedDispatcher /* imeDispatcher */,
+ true /* imeRequestedVisible */);
}
@Test