summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/notification.aconfig10
-rw-r--r--core/java/android/os/UserManager.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java71
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java26
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java1
-rw-r--r--packages/SystemUI/Android.bp2
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt61
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt54
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/system/QuickStepContractTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java11
-rw-r--r--packages/SystemUI/res/layout/battery_status_chip.xml12
-rw-r--r--packages/SystemUI/res/layout/status_bar_event_chip_compose.xml34
-rw-r--r--packages/SystemUI/res/values/dimens.xml4
-rw-r--r--packages/SystemUI/res/values/styles.xml13
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java5
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt100
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt95
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactory.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt30
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt2
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java3
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java290
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java61
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java23
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java35
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java3
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java4
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml4
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java414
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml11
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboardView.java10
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java67
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java51
-rw-r--r--services/tests/uiservicestests/Android.bp1
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java496
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java198
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java61
-rw-r--r--tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt21
65 files changed, 1833 insertions, 768 deletions
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 6fedcbe50f30..b9255ecaf1b6 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -126,6 +126,7 @@ interface INotificationManager
boolean areChannelsBypassingDnd();
ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int uid);
ParceledListSlice getPackagesBypassingDnd(int userId);
+ List<String> getPackagesWithAnyChannels(int userId);
boolean isPackagePaused(String pkg);
void deleteNotificationHistoryItem(String pkg, int uid, long postedTime);
boolean isPermissionFixed(String pkg, int userId);
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index a10b6ff39a37..9d8ab03982e6 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -308,6 +308,16 @@ flag {
}
flag {
+ name: "nm_binder_perf_get_apps_with_channels"
+ namespace: "systemui"
+ description: "Use a single binder call to get the set of apps with channels for a user"
+ bug: "362981561"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "no_sbnholder"
namespace: "systemui"
description: "removes sbnholder from NLS"
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c00f31db1a38..08365c935626 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -982,8 +982,8 @@ public class UserManager {
/**
* Specifies if a user is disallowed from adding new users. This can only be set by device
* owners or profile owners on the main user. The default value is <code>false</code>.
- * <p> When the device is an organization-owned device provisioned with a managed profile,
- * this restriction will be set as a base restriction which cannot be removed by any admin.
+ * <p> When the device is an organization-owned device, this restriction will be set as
+ * a base restriction which cannot be removed by any admin.
*
* <p>Holders of the permission
* {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
index 28227a1f8746..6be3c1f18b39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
@@ -189,14 +189,16 @@ public class BubbleTransitions {
*
* 1. Start inflating the bubble view
* 2. Once inflated (but not-yet visible), tell WM to do the shell-transition.
- * 3. Transition becomes ready, so notify Launcher
- * 4. Launcher responds with showExpandedView which calls continueExpand() to make view visible
- * 5. Surface is created which kicks off actual animation
+ * 3. When the transition becomes ready, notify Launcher in parallel
+ * 4. Wait for surface to be created
+ * 5. Once surface is ready, animate the task to a bubble
*
- * So, constructor -> onInflated -> startAnimation -> continueExpand -> surfaceCreated.
+ * While the animation is pending, we keep a reference to the pending transition in the bubble.
+ * This allows us to check in other parts of the code that this bubble will be shown via the
+ * transition animation.
*
- * continueExpand and surfaceCreated are set-up to happen in either order, though, to support
- * UX/timing adjustments.
+ * startAnimation, continueExpand and surfaceCreated are set-up to happen in either order,
+ * to support UX/timing adjustments.
*/
@VisibleForTesting
class ConvertToBubble implements Transitions.TransitionHandler, BubbleTransition {
@@ -209,9 +211,9 @@ public class BubbleTransitions {
final Rect mStartBounds = new Rect();
SurfaceControl mSnapshot = null;
TaskInfo mTaskInfo;
- boolean mFinishedExpand = false;
BubbleViewProvider mPriorBubble = null;
+ private final TransitionProgress mTransitionProgress = new TransitionProgress();
private SurfaceControl.Transaction mFinishT;
private SurfaceControl mTaskLeash;
@@ -359,12 +361,12 @@ public class BubbleTransitions {
startTransaction.apply();
mTaskViewTransitions.onExternalDone(transition);
+ mTransitionProgress.setTransitionReady();
+ startExpandAnim();
return true;
}
- @Override
- public void continueExpand() {
- mFinishedExpand = true;
+ private void startExpandAnim() {
final boolean animate = mLayerView.canExpandView(mBubble);
if (animate) {
mPriorBubble = mLayerView.prepareConvertedView(mBubble);
@@ -375,19 +377,25 @@ public class BubbleTransitions {
mLayerView.removeView(priorView);
mPriorBubble = null;
}
- if (!animate || mBubble.getTaskView().getSurfaceControl() != null) {
+ if (!animate || mTransitionProgress.isReadyToAnimate()) {
playAnimation(animate);
}
}
@Override
+ public void continueExpand() {
+ mTransitionProgress.setReadyToExpand();
+ }
+
+ @Override
public void surfaceCreated() {
+ mTransitionProgress.setSurfaceReady();
mMainExecutor.execute(() -> {
final TaskViewTaskController tvc = mBubble.getTaskView().getController();
final TaskViewRepository.TaskViewState state = mRepository.byTaskView(tvc);
if (state == null) return;
state.mVisible = true;
- if (mFinishedExpand) {
+ if (mTransitionProgress.isReadyToAnimate()) {
playAnimation(true /* animate */);
}
});
@@ -403,9 +411,6 @@ public class BubbleTransitions {
mFinishWct = null;
}
- // Preparation is complete.
- mBubble.setPreparingTransition(null);
-
if (animate) {
mLayerView.animateConvert(startT, mStartBounds, mSnapshot, mTaskLeash, () -> {
mFinishCb.onTransitionFinished(mFinishWct);
@@ -417,6 +422,42 @@ public class BubbleTransitions {
mFinishCb = null;
}
}
+
+ /**
+ * Keeps track of internal state of different steps of this BubbleTransition.
+ */
+ private class TransitionProgress {
+ private boolean mTransitionReady;
+ private boolean mReadyToExpand;
+ private boolean mSurfaceReady;
+
+ void setTransitionReady() {
+ mTransitionReady = true;
+ onUpdate();
+ }
+
+ void setReadyToExpand() {
+ mReadyToExpand = true;
+ onUpdate();
+ }
+
+ void setSurfaceReady() {
+ mSurfaceReady = true;
+ onUpdate();
+ }
+
+ boolean isReadyToAnimate() {
+ // Animation only depends on transition and surface state
+ return mTransitionReady && mSurfaceReady;
+ }
+
+ private void onUpdate() {
+ if (mTransitionReady && mReadyToExpand && mSurfaceReady) {
+ // Clear the transition from bubble when all the steps are ready
+ mBubble.setPreparingTransition(null);
+ }
+ }
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
index 8cdb8c4512a9..f8d84e4f3c21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
@@ -59,11 +59,12 @@ oneway interface IRecentsAnimationRunner {
void onAnimationStart(in IRecentsAnimationController controller,
in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
in Rect homeContentInsets, in Rect minimizedHomeBounds, in Bundle extras,
- in TransitionInfo info) = 2;
+ in @nullable TransitionInfo info) = 2;
/**
* Called when the task of an activity that has been started while the recents animation
* was running becomes ready for control.
*/
- void onTasksAppeared(in RemoteAnimationTarget[] app) = 3;
+ void onTasksAppeared(in RemoteAnimationTarget[] app,
+ in @nullable TransitionInfo transitionInfo) = 3;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 7751741ae082..a969845fb8e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -1214,13 +1214,16 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
// Since we're accepting the merge, update the finish transaction so that changes via
// that transaction will be applied on top of those of the merged transitions
mFinishTransaction = finishT;
- // not using the incoming anim-only surfaces
- info.releaseAnimSurfaces();
+ boolean passTransitionInfo = ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue();
+ if (!passTransitionInfo) {
+ // not using the incoming anim-only surfaces
+ info.releaseAnimSurfaces();
+ }
if (appearedTargets != null) {
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.merge: calling onTasksAppeared", mInstanceId);
- mListener.onTasksAppeared(appearedTargets);
+ mListener.onTasksAppeared(appearedTargets, passTransitionInfo ? info : null);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
index b5911bfa54f2..87ee4f58bfdd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
@@ -186,15 +186,20 @@ public class BubbleTransitionsTest extends ShellTestCase {
verify(startT).setPosition(any(), eq(0f), eq(0f));
verify(mBubbleData).notificationEntryUpdated(eq(mBubble), anyBoolean(), anyBoolean());
- ctb.continueExpand();
clearInvocations(mBubble);
verify(mBubble, never()).setPreparingTransition(any());
ctb.surfaceCreated();
- verify(mBubble).setPreparingTransition(isNull());
+ // Check that preparing transition is not reset before continueExpand is called
+ verify(mBubble, never()).setPreparingTransition(any());
ArgumentCaptor<Runnable> animCb = ArgumentCaptor.forClass(Runnable.class);
verify(mLayerView).animateConvert(any(), any(), any(), any(), animCb.capture());
+
+ // continueExpand is now called, check that preparing transition is cleared
+ ctb.continueExpand();
+ verify(mBubble).setPreparingTransition(isNull());
+
assertFalse(finishCalled[0]);
animCb.getValue().run();
assertTrue(finishCalled[0]);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index 439be9155b26..fd5e567f69ed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -34,6 +34,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -293,6 +294,31 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+ public void testMerge_openingTasks_callsOnTasksAppeared() throws Exception {
+ final IRecentsAnimationRunner animationRunner = mock(IRecentsAnimationRunner.class);
+ TransitionInfo mergeTransitionInfo = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, new TestRunningTaskInfoBuilder().build())
+ .build();
+ final IBinder transition = startRecentsTransition(/* synthetic= */ false, animationRunner);
+ SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mRecentsTransitionHandler.startAnimation(
+ transition, createTransitionInfo(), new StubTransaction(), new StubTransaction(),
+ mock(Transitions.TransitionFinishCallback.class));
+
+ mRecentsTransitionHandler.findController(transition).merge(
+ mergeTransitionInfo,
+ new StubTransaction(),
+ finishT,
+ transition,
+ mock(Transitions.TransitionFinishCallback.class));
+ mMainExecutor.flushAll();
+
+ verify(animationRunner).onTasksAppeared(
+ /* appearedTargets= */ any(), eq(mergeTransitionInfo));
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
public void testMergeAndFinish_openingFreeformTasks_setsCornerRadius() {
ActivityManager.RunningTaskInfo freeformTask =
new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
index 74fd828f97ea..274fd3593918 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
@@ -124,6 +124,7 @@ final class WritableNamespacePrefixes {
"privacy",
"private_compute_services",
"profcollect_native_boot",
+ "profiling_testing",
"remote_auth",
"remote_key_provisioning_native",
"rollback",
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 08c3e91744b2..49cdec11e104 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -850,7 +850,7 @@ java_library {
"androidx.test.uiautomator_uiautomator",
"androidx.core_core-animation-testing",
"androidx.test.ext.junit",
- "inline-mockito-robolectric-prebuilt",
+ "inline-mockito5-robolectric-prebuilt",
"mockito-kotlin-nodeps",
"platform-parametric-runner-lib",
"SystemUICustomizationTestUtils",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 910f71276376..511e54b9abff 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1258,13 +1258,6 @@ flag {
}
flag {
- name: "glanceable_hub_back_action"
- namespace: "systemui"
- description: "Support back action from glanceable hub"
- bug: "382771533"
-}
-
-flag {
name: "dream_overlay_updated_font"
namespace: "systemui"
description: "Flag to enable updated font settings for dream overlay"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
index 8b0c00535262..09db2d653326 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
@@ -19,10 +19,12 @@ package com.android.systemui.common.ui.compose
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
-import com.android.compose.ui.graphics.painter.rememberDrawablePainter
+import androidx.core.graphics.drawable.toBitmap
import com.android.systemui.common.shared.model.Icon
/**
@@ -35,7 +37,12 @@ fun Icon(icon: Icon, modifier: Modifier = Modifier, tint: Color = LocalContentCo
val contentDescription = icon.contentDescription?.load()
when (icon) {
is Icon.Loaded -> {
- Icon(rememberDrawablePainter(icon.drawable), contentDescription, modifier, tint)
+ Icon(
+ remember(icon.drawable) { icon.drawable.toBitmap().asImageBitmap() },
+ contentDescription,
+ modifier,
+ tint,
+ )
}
is Icon.Resource -> Icon(painterResource(icon.res), contentDescription, modifier, tint)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 4d238ac3798d..8c5fad3906ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.back.domain.interactor
-import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
@@ -32,7 +31,6 @@ import androidx.test.filters.SmallTest
import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.domain.interactor.CommunalBackActionInteractor
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
@@ -93,7 +91,6 @@ class BackActionInteractorTest : SysuiTestCase() {
@Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
@Mock private lateinit var iStatusBarService: IStatusBarService
@Mock private lateinit var headsUpManager: HeadsUpManager
- @Mock private lateinit var communalBackActionInteractor: CommunalBackActionInteractor
private val keyguardRepository = FakeKeyguardRepository()
private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
@@ -118,7 +115,6 @@ class BackActionInteractorTest : SysuiTestCase() {
windowRootViewVisibilityInteractor,
shadeBackActionInteractor,
qsController,
- communalBackActionInteractor,
)
}
@@ -297,19 +293,6 @@ class BackActionInteractorTest : SysuiTestCase() {
verify(shadeBackActionInteractor).onBackProgressed(0.4f)
}
- @Test
- @EnableFlags(Flags.FLAG_GLANCEABLE_HUB_BACK_ACTION)
- fun onBackAction_communalCanBeDismissed_communalBackActionInteractorCalled() {
- backActionInteractor.start()
- windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
- powerInteractor.setAwakeForTest()
- val callback = getBackInvokedCallback()
- whenever(communalBackActionInteractor.canBeDismissed()).thenReturn(true)
- callback.onBackInvoked()
-
- verify(communalBackActionInteractor).onBackPressed()
- }
-
private fun getBackInvokedCallback(): OnBackInvokedCallback {
testScope.runCurrent()
val captor = argumentCaptor<OnBackInvokedCallback>()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt
deleted file mode 100644
index 70f38f7bc94e..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt
+++ /dev/null
@@ -1,61 +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 android.platform.test.annotations.EnableFlags
-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.communalSceneRepository
-import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.runCurrent
-import com.android.systemui.kosmos.runTest
-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 CommunalBackActionInteractorTest : SysuiTestCase() {
- private val kosmos = testKosmos()
-
- private var Kosmos.underTest by Fixture { communalBackActionInteractor }
-
- @Test
- @EnableFlags(FLAG_COMMUNAL_HUB)
- fun communalShowing_canBeDismissed() =
- kosmos.runTest {
- setCommunalAvailable(true)
- assertThat(underTest.canBeDismissed()).isEqualTo(false)
- communalInteractor.changeScene(CommunalScenes.Communal, "test")
- runCurrent()
- assertThat(underTest.canBeDismissed()).isEqualTo(true)
- }
-
- @Test
- @EnableFlags(FLAG_COMMUNAL_HUB)
- fun onBackPressed_invokesSceneChange() =
- kosmos.runTest {
- underTest.onBackPressed()
- runCurrent()
- assertThat(communalSceneRepository.currentScene.value).isEqualTo(CommunalScenes.Blank)
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index feee9e3d62d2..6eace1b50ea7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -128,6 +128,19 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
}
@Test
+ fun tutorialState_startedAndCommunalSceneShowing_stateWillNotUpdate() =
+ testScope.runTest {
+ val tutorialSettingState by
+ collectLastValue(communalTutorialRepository.tutorialSettingState)
+
+ communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
+
+ goToCommunal()
+
+ assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
+ }
+
+ @Test
fun tutorialState_completedAndCommunalSceneShowing_stateWillNotUpdate() =
testScope.runTest {
val tutorialSettingState by
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt
new file mode 100644
index 000000000000..052dfd52887f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.collectValues
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+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 DozingToDreamingTransitionViewModelTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+
+ val underTest by lazy { kosmos.dozingToDreamingTransitionViewModel }
+
+ @Test
+ fun notificationShadeAlpha() =
+ kosmos.runTest {
+ val values by collectValues(underTest.notificationAlpha)
+ assertThat(values).isEmpty()
+
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+
+ assertThat(values).isNotEmpty()
+ values.forEach { assertThat(it).isEqualTo(0) }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/system/QuickStepContractTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/system/QuickStepContractTest.kt
index d92781a5f3ce..ef03fab95778 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/system/QuickStepContractTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/system/QuickStepContractTest.kt
@@ -16,10 +16,8 @@
package com.android.systemui.shared.system
-import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_BACK_ACTION
import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_COMMUNAL_HUB_SHOWING
@@ -32,7 +30,6 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class QuickStepContractTest : SysuiTestCase() {
@Test
- @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_ACTION)
fun isBackGestureDisabled_hubShowing() {
val sysuiStateFlags = SYSUI_STATE_COMMUNAL_HUB_SHOWING
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 6f785a3731e1..dde6e2ee1866 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -65,11 +65,9 @@ import com.android.keyguard.KeyguardMessageArea;
import com.android.keyguard.KeyguardMessageAreaController;
import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
@@ -146,7 +144,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private View mNotificationContainer;
- @Mock private KeyguardBypassController mBypassController;
@Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
@Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
@Mock private KeyguardMessageArea mKeyguardMessageArea;
@@ -158,7 +155,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
@Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
- @Mock private UdfpsOverlayInteractor mUdfpsOverlayInteractor;
@Mock private ActivityStarter mActivityStarter;
@Mock private BouncerView mBouncerView;
@Mock private BouncerViewDelegate mBouncerViewDelegate;
@@ -167,7 +163,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock private WindowInsetsController mWindowInsetsController;
@Mock private TaskbarDelegate mTaskbarDelegate;
- @Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
@Mock private DeviceEntryInteractor mDeviceEntryInteractor;
@Mock private SceneInteractor mSceneInteractor;
@@ -190,8 +185,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Captor
private ArgumentCaptor<OnBackInvokedCallback> mBackCallbackCaptor;
- @Captor
- private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
@Mock
private KeyguardDismissActionInteractor mKeyguardDismissActionInteractor;
@@ -227,7 +220,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mock(DockManager.class),
mNotificationShadeWindowController,
mKeyguardStateController,
- mKeyguardMessageAreaFactory,
Optional.of(mSysUiUnfoldComponent),
() -> mShadeController,
mLatencyTracker,
@@ -236,7 +228,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mPrimaryBouncerInteractor,
mBouncerView,
mAlternateBouncerInteractor,
- mUdfpsOverlayInteractor,
mActivityStarter,
mKeyguardTransitionInteractor,
mock(KeyguardDismissTransitionInteractor.class),
@@ -732,7 +723,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mock(DockManager.class),
mock(NotificationShadeWindowController.class),
mKeyguardStateController,
- mKeyguardMessageAreaFactory,
Optional.of(mSysUiUnfoldComponent),
() -> mShadeController,
mLatencyTracker,
@@ -741,7 +731,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mPrimaryBouncerInteractor,
mBouncerView,
mAlternateBouncerInteractor,
- mUdfpsOverlayInteractor,
mActivityStarter,
mock(KeyguardTransitionInteractor.class),
mock(KeyguardDismissTransitionInteractor.class),
diff --git a/packages/SystemUI/res/layout/battery_status_chip.xml b/packages/SystemUI/res/layout/battery_status_chip.xml
index 74371839e247..7399651d4248 100644
--- a/packages/SystemUI/res/layout/battery_status_chip.xml
+++ b/packages/SystemUI/res/layout/battery_status_chip.xml
@@ -24,21 +24,13 @@
<LinearLayout
android:id="@+id/rounded_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/ongoing_appops_chip_height"
- android:layout_gravity="center"
- android:background="@drawable/statusbar_chip_bg"
- android:clipToOutline="true"
- android:gravity="center"
- android:maxWidth="@dimen/ongoing_appops_chip_max_width"
- android:minWidth="@dimen/ongoing_appops_chip_min_width">
+ style="@style/StatusBar.EventChip">
<com.android.systemui.battery.BatteryMeterView
android:id="@+id/battery_meter_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginHorizontal="10dp" />
+ android:layout_marginHorizontal="@dimen/ongoing_appops_chip_content_horizontal_margin" />
</LinearLayout>
</merge> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_event_chip_compose.xml b/packages/SystemUI/res/layout/status_bar_event_chip_compose.xml
new file mode 100644
index 000000000000..ff96ab15cd15
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_event_chip_compose.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical|end">
+
+ <LinearLayout
+ android:id="@+id/rounded_container"
+ style="@style/StatusBar.EventChip">
+
+ <!-- Stub for the composable -->
+ <androidx.compose.ui.platform.ComposeView
+ android:id="@+id/compose_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/ongoing_appops_chip_content_horizontal_margin" />
+
+ </LinearLayout>
+</merge>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c1fcbfd4de70..12a086939e53 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1243,7 +1243,7 @@
<dimen name="max_window_blur_radius">23px</dimen>
<!-- Blur radius behind Notification Shade -->
- <dimen name="max_shade_window_blur_radius">60dp</dimen>
+ <dimen name="max_shade_window_blur_radius">34dp</dimen>
<!-- How much into a DisplayCutout's bounds we can go, on each side -->
<dimen name="display_cutout_margin_consumption">0px</dimen>
@@ -1254,6 +1254,8 @@
<dimen name="ongoing_appops_chip_side_padding">8dp</dimen>
<!-- Margin between icons of Ongoing App Ops chip -->
<dimen name="ongoing_appops_chip_icon_margin">4dp</dimen>
+ <!-- Side margins for the content of an appops chip -->
+ <dimen name="ongoing_appops_chip_content_horizontal_margin">10dp</dimen>
<!-- Icon size of Ongoing App Ops chip -->
<dimen name="ongoing_appops_chip_icon_size">16sp</dimen>
<!-- Radius of Ongoing App Ops chip corners -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4961a7ece69a..8f808d389203 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -93,6 +93,19 @@
<item name="android:textColor">?android:attr/colorPrimary</item>
</style>
+ <style name="StatusBar.EventChip">
+ <item name="android:orientation">horizontal</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_gravity">center</item>
+ <item name="android:gravity">center</item>
+ <item name="android:clipToOutline">true</item>
+ <item name="android:background">@drawable/statusbar_chip_bg</item>
+ <item name="android:minHeight">@dimen/ongoing_appops_chip_height</item>
+ <item name="android:maxWidth">@dimen/ongoing_appops_chip_max_width</item>
+ <item name="android:minWidth">@dimen/ongoing_appops_chip_min_width</item>
+ </style>
+
<style name="Chipbar" />
<style name="Chipbar.Text" parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 82ac78c6db15..0372a6c6d2c5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -20,7 +20,6 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
-import static com.android.systemui.Flags.glanceableHubBackAction;
import static com.android.systemui.shared.Flags.shadeAllowBackGesture;
import android.annotation.LongDef;
@@ -361,10 +360,6 @@ public class QuickStepContract {
}
// Disable back gesture on the hub, but not when the shade is showing.
if ((sysuiStateFlags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
- // Allow back gesture on Glanceable Hub with back action support.
- if (glanceableHubBackAction()) {
- return false;
- }
// Use QS expanded signal as the notification panel is always considered visible
// expanded when on the lock screen and when opening hub over lock screen. This does
// mean that back gesture is disabled when opening shade over hub while in portrait
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index ff6bcdb150f8..fcde508b07a8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -16,6 +16,7 @@
package com.android.systemui.shared.system;
+import android.annotation.Nullable;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.RemoteAnimationTarget;
@@ -42,5 +43,5 @@ public interface RecentsAnimationListener {
* Called when the task of an activity that has been started while the recents animation
* was running becomes ready for control.
*/
- void onTasksAppeared(RemoteAnimationTarget[] app);
+ void onTasksAppeared(RemoteAnimationTarget[] app, @Nullable TransitionInfo transitionInfo);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 5a9cbce73e4b..892851cd7056 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -173,6 +173,11 @@ public interface KeyguardViewController {
boolean isBouncerShowing();
/**
+ * Report when the UI is ready for dismissing the whole Keyguard.
+ */
+ void readyForKeyguardDone();
+
+ /**
* Stop showing the alternate bouncer, if showing.
*
* <p>Should be like calling {@link #hideAlternateBouncer(boolean, boolean)} with a {@code true}
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 11a6cb9334ae..0b578c65e915 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -23,9 +23,7 @@ import android.window.OnBackInvokedDispatcher
import android.window.WindowOnBackInvokedDispatcher
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
-import com.android.systemui.Flags.glanceableHubBackAction
import com.android.systemui.Flags.predictiveBackAnimateShade
-import com.android.systemui.communal.domain.interactor.CommunalBackActionInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -52,7 +50,6 @@ constructor(
private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor,
private val shadeBackActionInteractor: ShadeBackActionInteractor,
private val qsController: QuickSettingsController,
- private val communalBackActionInteractor: CommunalBackActionInteractor,
) : CoreStartable {
private var isCallbackRegistered = false
@@ -114,12 +111,6 @@ constructor(
shadeBackActionInteractor.animateCollapseQs(false)
return true
}
- if (glanceableHubBackAction()) {
- if (communalBackActionInteractor.canBeDismissed()) {
- communalBackActionInteractor.onBackPressed()
- return true
- }
- }
if (shouldBackBeHandled()) {
if (shadeBackActionInteractor.canBeCollapsed()) {
// this is the Shade dismiss animation, so make sure QQS closes when it ends.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt
deleted file mode 100644
index 2ccf96abff79..000000000000
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt
+++ /dev/null
@@ -1,56 +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 com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.model.Scenes
-import javax.inject.Inject
-
-/**
- * {@link CommunalBackActionInteractor} is responsible for handling back gestures on the glanceable
- * hub. When invoked SystemUI should navigate back to the lockscreen.
- */
-@SysUISingleton
-class CommunalBackActionInteractor
-@Inject
-constructor(
- private val communalInteractor: CommunalInteractor,
- private val communalSceneInteractor: CommunalSceneInteractor,
- private val sceneInteractor: SceneInteractor,
-) {
- fun canBeDismissed(): Boolean {
- return communalInteractor.isCommunalShowing.value
- }
-
- fun onBackPressed() {
- if (SceneContainerFlag.isEnabled) {
- // TODO(b/384610333): Properly determine whether to go to dream or lockscreen on back.
- sceneInteractor.changeScene(
- toScene = Scenes.Lockscreen,
- loggingReason = "CommunalBackActionInteractor",
- )
- } else {
- communalSceneInteractor.changeScene(
- newScene = CommunalScenes.Blank,
- loggingReason = "CommunalBackActionInteractor",
- )
- }
- }
-}
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 6dab32a66c94..564628d3f52f 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
@@ -327,7 +327,7 @@ constructor(
* use [isIdleOnCommunal].
*/
// TODO(b/323215860): rename to something more appropriate after cleaning up usages
- val isCommunalShowing: StateFlow<Boolean> =
+ val isCommunalShowing: Flow<Boolean> =
flow { emit(SceneContainerFlag.isEnabled) }
.flatMapLatest { sceneContainerEnabled ->
if (sceneContainerEnabled) {
@@ -345,10 +345,10 @@ constructor(
columnName = "isCommunalShowing",
initialValue = false,
)
- .stateIn(
+ .shareIn(
scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = false,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index a56a63c0b104..3132ec2b98e3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -53,7 +53,6 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.Flags;
import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.ambient.touch.TouchMonitor;
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
@@ -211,7 +210,6 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mCommunalVisible = communalVisible;
updateLifecycleStateLocked();
- updateGestureBlockingLocked();
});
}
};
@@ -594,8 +592,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
private void updateGestureBlockingLocked() {
final boolean shouldBlock = mStarted && !mShadeExpanded && !mBouncerShowing
- && !isDreamInPreviewMode()
- && !(Flags.glanceableHubBackAction() && mCommunalVisible);
+ && !isDreamInPreviewMode();
if (shouldBlock) {
mGestureInteractor.addGestureBlockedMatcher(DREAM_TYPE_MATCHER,
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..0700ec639153 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
@@ -159,6 +159,7 @@ constructor(
val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
val isKeyguardGoingAway = keyguardInteractor.isKeyguardGoingAway.value
+ val canStartDreaming = dreamManager.canStartDreaming(false)
if (!deviceEntryInteractor.isLockscreenEnabled()) {
if (!SceneContainerFlag.isEnabled) {
@@ -191,6 +192,13 @@ constructor(
if (!SceneContainerFlag.isEnabled) {
transitionToGlanceableHub()
}
+ } else if (canStartDreaming) {
+ // If we're waking up to dream, transition directly to dreaming without
+ // showing the lockscreen.
+ startTransitionTo(
+ KeyguardState.DREAMING,
+ ownerReason = "moving from doze to dream",
+ )
} else {
startTransitionTo(KeyguardState.LOCKSCREEN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt
index e6a85c6860c5..9018c58a7e36 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt
@@ -39,4 +39,6 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) {
)
val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f)
+ // Notifications should not be shown while transitioning to dream.
+ val notificationAlpha = transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
index b271c6979b31..71977ef1f234 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
@@ -16,19 +16,19 @@
package com.android.systemui.shade
+import com.android.keyguard.KeyguardViewController
import com.android.systemui.assist.AssistManager
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import dagger.Lazy
/** A base class for non-empty implementations of ShadeController. */
abstract class BaseShadeControllerImpl(
protected val commandQueue: CommandQueue,
- protected val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ protected val keyguardViewController: KeyguardViewController,
protected val notificationShadeWindowController: NotificationShadeWindowController,
- protected val assistManagerLazy: Lazy<AssistManager>
+ protected val assistManagerLazy: Lazy<AssistManager>,
) : ShadeController {
protected lateinit var notifPresenter: NotificationPresenter
/** Runnables to run after completing a collapse of the shade. */
@@ -66,7 +66,7 @@ abstract class BaseShadeControllerImpl(
for (r in clonedList) {
r.run()
}
- statusBarKeyguardViewManager.readyForKeyguardDone()
+ keyguardViewController.readyForKeyguardDone()
}
final override fun onLaunchAnimationEnd(launchIsFullScreen: Boolean) {
@@ -77,6 +77,7 @@ abstract class BaseShadeControllerImpl(
instantCollapseShade()
}
}
+
final override fun onLaunchAnimationCancelled(isLaunchForActivity: Boolean) {
if (
notifPresenter.isPresenterFullyCollapsed() &&
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 0e30f2b4bb30..acae1bc81b97 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -23,6 +23,7 @@ import android.view.MotionEvent;
import android.view.ViewTreeObserver;
import android.view.WindowManagerGlobal;
+import com.android.keyguard.KeyguardViewController;
import com.android.systemui.DejankUtils;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
@@ -35,7 +36,6 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
@@ -61,7 +61,6 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl {
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarStateController mStatusBarStateController;
- private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final StatusBarWindowControllerStore mStatusBarWindowControllerStore;
private final DeviceProvisionedController mDeviceProvisionedController;
@@ -82,7 +81,7 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl {
WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ KeyguardViewController keyguardViewController,
StatusBarWindowControllerStore statusBarWindowControllerStore,
DeviceProvisionedController deviceProvisionedController,
NotificationShadeWindowController notificationShadeWindowController,
@@ -93,7 +92,7 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl {
Lazy<NotificationGutsManager> gutsManager
) {
super(commandQueue,
- statusBarKeyguardViewManager,
+ keyguardViewController,
notificationShadeWindowController,
assistManagerLazy);
SceneContainerFlag.assertInLegacyMode();
@@ -107,7 +106,6 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl {
mGutsManager = gutsManager;
mNotificationShadeWindowController = notificationShadeWindowController;
mNotifShadeWindowViewController = notificationShadeWindowViewController;
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mDisplayId = displayId;
mKeyguardStateController = keyguardStateController;
mAssistManagerLazy = assistManagerLazy;
@@ -396,7 +394,7 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl {
@Override
public void collapseShadeForActivityStart() {
- if (isExpandedVisible() && !mStatusBarKeyguardViewManager.isBouncerShowing()) {
+ if (isExpandedVisible() && !getKeyguardViewController().isBouncerShowing()) {
animateCollapseShadeForcedDelayed();
} else {
// Do it after DismissAction has been processed to conserve the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
index a58ce4162ddc..02cec13d2ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
@@ -26,6 +26,7 @@ import com.android.settingslib.flags.Flags.newStatusBarIcons
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.unified.BatteryColors
import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.events.BackgroundAnimatableView
class BatteryStatusChip @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
@@ -37,6 +38,8 @@ class BatteryStatusChip @JvmOverloads constructor(context: Context, attrs: Attri
get() = batteryMeterView
init {
+ NewStatusBarIcons.assertInLegacyMode()
+
inflate(context, R.layout.battery_status_chip, this)
roundedContainer = requireViewById(R.id.rounded_container)
batteryMeterView = requireViewById(R.id.battery_meter_view)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
index ea1d7820c79c..5887eb6ad865 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -25,6 +25,8 @@ import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyItem
import com.android.systemui.statusbar.BatteryStatusChip
import com.android.systemui.statusbar.ConnectedDisplayChip
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.events.ui.view.BatteryStatusEventComposeChip
typealias ViewCreator = (context: Context) -> BackgroundAnimatableView
@@ -53,9 +55,7 @@ interface StatusEvent {
}
}
-class BGView(
- context: Context
-) : View(context), BackgroundAnimatableView {
+class BGView(context: Context) : View(context), BackgroundAnimatableView {
override val view: View
get() = this
@@ -65,9 +65,7 @@ class BGView(
}
@SuppressLint("AppCompatCustomView")
-class BGImageView(
- context: Context
-) : ImageView(context), BackgroundAnimatableView {
+class BGImageView(context: Context) : ImageView(context), BackgroundAnimatableView {
override val view: View
get() = this
@@ -84,8 +82,10 @@ class BatteryEvent(@IntRange(from = 0, to = 100) val batteryLevel: Int) : Status
override val shouldAnnounceAccessibilityEvent: Boolean = false
override val viewCreator: ViewCreator = { context ->
- BatteryStatusChip(context).apply {
- setBatteryLevel(batteryLevel)
+ if (NewStatusBarIcons.isEnabled) {
+ BatteryStatusEventComposeChip(batteryLevel, context)
+ } else {
+ BatteryStatusChip(context).apply { setBatteryLevel(batteryLevel) }
}
}
@@ -103,9 +103,7 @@ class ConnectedDisplayEvent : StatusEvent {
override var contentDescription: String? = ""
override val shouldAnnounceAccessibilityEvent: Boolean = true
- override val viewCreator: ViewCreator = { context ->
- ConnectedDisplayChip(context)
- }
+ override val viewCreator: ViewCreator = { context -> ConnectedDisplayChip(context) }
override fun toString(): String {
return javaClass.simpleName
@@ -134,7 +132,8 @@ open class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEven
}
override fun shouldUpdateFromEvent(other: StatusEvent?): Boolean {
- return other is PrivacyEvent && (other.privacyItems != privacyItems ||
+ return other is PrivacyEvent &&
+ (other.privacyItems != privacyItems ||
other.contentDescription != contentDescription ||
(other.forceVisible && !forceVisible))
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt
new file mode 100644
index 000000000000..a90e3ff4b2dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.statusbar.events.ui.view
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.events.BackgroundAnimatableView
+import com.android.systemui.statusbar.pipeline.battery.domain.interactor.BatteryInteractor
+import com.android.systemui.statusbar.pipeline.battery.shared.ui.BatteryColors.LightThemeChargingColors
+import com.android.systemui.statusbar.pipeline.battery.shared.ui.BatteryFrame
+import com.android.systemui.statusbar.pipeline.battery.shared.ui.BatteryGlyph
+import com.android.systemui.statusbar.pipeline.battery.ui.composable.BatteryCanvas
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel.Companion.glyphRepresentation
+
+/**
+ * [StatusEvent] chip for the battery plugged in status event. Shows the current battery level and
+ * charging state in the status bar via the system event animation.
+ *
+ * This chip will fully replace [BatteryStatusChip] when [NewStatusBarIcons] is rolled out
+ */
+@SuppressLint("ViewConstructor")
+class BatteryStatusEventComposeChip
+@JvmOverloads
+constructor(level: Int, context: Context, attrs: AttributeSet? = null) :
+ FrameLayout(context, attrs), BackgroundAnimatableView {
+ private val roundedContainer: LinearLayout
+ private val composeInner: ComposeView
+ override val contentView: View
+ get() = composeInner
+
+ init {
+ NewStatusBarIcons.assertInNewMode()
+
+ inflate(context, R.layout.status_bar_event_chip_compose, this)
+ roundedContainer = requireViewById(R.id.rounded_container)
+ composeInner = requireViewById(R.id.compose_view)
+ composeInner.apply {
+ setContent {
+ val isFull = BatteryInteractor.isBatteryFull(level)
+ BatteryCanvas(
+ modifier =
+ Modifier.width(BatteryViewModel.STATUS_BAR_BATTERY_WIDTH)
+ .height(BatteryViewModel.STATUS_BAR_BATTERY_HEIGHT),
+ path = BatteryFrame.pathSpec,
+ // TODO(b/394659067): get a content description for this chip
+ contentDescription = "",
+ innerWidth = BatteryFrame.innerWidth,
+ innerHeight = BatteryFrame.innerHeight,
+ // This event only happens when plugged in, so we always show it as charging
+ glyphs =
+ if (isFull) listOf(BatteryGlyph.Bolt)
+ else level.glyphRepresentation() + BatteryGlyph.Bolt,
+ level = level,
+ isFull = isFull,
+ colorsProvider = { LightThemeChargingColors },
+ )
+ }
+ }
+ updateResources()
+ }
+
+ /**
+ * When animating as a chip in the status bar, we want to animate the width for the rounded
+ * container. We have to subtract our own top and left offset because the bounds come to us as
+ * absolute on-screen bounds.
+ */
+ override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {
+ roundedContainer.setLeftTopRightBottom(l - left, t - top, r - left, b - top)
+ }
+
+ @SuppressLint("UseCompatLoadingForDrawables")
+ private fun updateResources() {
+ roundedContainer.background = mContext.getDrawable(R.drawable.statusbar_chip_bg)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 54efa4a2bcf2..2c8c7a1bdd44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -48,6 +48,7 @@ import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToPrimaryBouncerTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
@@ -136,6 +137,7 @@ constructor(
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel,
+ private val dozingToDreamingTransitionViewModel: DozingToDreamingTransitionViewModel,
dozingToGlanceableHubTransitionViewModel: DozingToGlanceableHubTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
@@ -572,6 +574,7 @@ constructor(
aodToLockscreenTransitionViewModel.notificationAlpha,
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
aodToPrimaryBouncerTransitionViewModel.notificationAlpha,
+ dozingToDreamingTransitionViewModel.notificationAlpha,
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dozingToPrimaryBouncerTransitionViewModel.notificationAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 01de925f3d78..bc297699c41a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -46,7 +46,6 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardMessageAreaController;
import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -56,7 +55,6 @@ import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.DejankUtils;
import com.android.systemui.Flags;
import com.android.systemui.animation.back.FlingOnBackAnimationCallback;
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
@@ -158,7 +156,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private final ConfigurationController mConfigurationController;
private final NavigationModeController mNavigationModeController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
private final DreamOverlayStateController mDreamOverlayStateController;
@Nullable
private final FoldAodAnimationController mFoldAodAnimationController;
@@ -328,7 +325,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private float mQsExpansion;
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
- private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
private final ActivityStarter mActivityStarter;
private OnDismissAction mAfterKeyguardGoneAction;
@@ -386,7 +382,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
DockManager dockManager,
NotificationShadeWindowController notificationShadeWindowController,
KeyguardStateController keyguardStateController,
- KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
Optional<SysUIUnfoldComponent> sysUIUnfoldComponent,
Lazy<ShadeController> shadeController,
LatencyTracker latencyTracker,
@@ -395,7 +390,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
PrimaryBouncerInteractor primaryBouncerInteractor,
BouncerView primaryBouncerView,
AlternateBouncerInteractor alternateBouncerInteractor,
- UdfpsOverlayInteractor udfpsOverlayInteractor,
ActivityStarter activityStarter,
KeyguardTransitionInteractor keyguardTransitionInteractor,
KeyguardDismissTransitionInteractor keyguardDismissTransitionInteractor,
@@ -423,7 +417,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mKeyguardUpdateManager = keyguardUpdateMonitor;
mStatusBarStateController = sysuiStatusBarStateController;
mDockManager = dockManager;
- mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
mShadeController = shadeController;
mLatencyTracker = latencyTracker;
mKeyguardSecurityModel = keyguardSecurityModel;
@@ -434,7 +427,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
mAlternateBouncerInteractor = alternateBouncerInteractor;
mBouncerInteractor = bouncerInteractor;
- mUdfpsOverlayInteractor = udfpsOverlayInteractor;
mActivityStarter = activityStarter;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mKeyguardDismissTransitionInteractor = keyguardDismissTransitionInteractor;
@@ -1581,6 +1573,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
&& mPrimaryBouncerView.getDelegate().dispatchBackKeyEventPreIme();
}
+ @Override
public void readyForKeyguardDone() {
mViewMediatorCallback.readyForKeyguardDone();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt
index 8fdb6ee57587..d53cbabb1d19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt
@@ -29,7 +29,7 @@ class BatteryInteractor @Inject constructor(repo: BatteryRepository) {
val level = repo.level.filterNotNull()
/** Whether the battery has been fully charged */
- val isFull = level.map { it >= 100 }
+ val isFull = level.map { isBatteryFull(it) }
/**
* For the sake of battery views, consider it to be "charging" if plugged in. This allows users
@@ -82,6 +82,8 @@ class BatteryInteractor @Inject constructor(repo: BatteryRepository) {
companion object {
/** Level below which we consider to be critically low */
private const val CRITICAL_LEVEL = 20
+
+ fun isBatteryFull(level: Int) = level >= 100
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index 71ddcf65b7b6..98042d5022f9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -17,18 +17,20 @@
package com.android.systemui.volume.dialog.ui.binder
import android.app.Dialog
-import android.content.res.Resources
import android.view.View
import android.view.ViewTreeObserver
import android.view.WindowInsets
+import androidx.compose.ui.util.lerp
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.view.updatePadding
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
import com.android.internal.view.RotationPolicy
import com.android.systemui.common.ui.view.onApplyWindowInsets
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.util.kotlin.awaitCancellationThenDispose
-import com.android.systemui.volume.SystemUIInterpolators
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
@@ -36,6 +38,7 @@ import com.android.systemui.volume.dialog.ui.utils.suspendAnimate
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel
import com.android.systemui.volume.dialog.utils.VolumeTracer
import javax.inject.Inject
+import kotlin.math.ceil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -47,24 +50,25 @@ import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
+private const val SPRING_STIFFNESS = 700f
+private const val SPRING_DAMPING_RATIO = 0.9f
+
+private const val FRACTION_HIDE = 0f
+private const val FRACTION_SHOW = 1f
+private const val ANIMATION_MINIMUM_VISIBLE_CHANGE = 0.01f
+
/** Binds the root view of the Volume Dialog. */
@OptIn(ExperimentalCoroutinesApi::class)
@VolumeDialogScope
class VolumeDialogViewBinder
@Inject
constructor(
- @Main resources: Resources,
private val viewModel: VolumeDialogViewModel,
private val jankListenerFactory: JankListenerFactory,
private val tracer: VolumeTracer,
private val viewBinders: List<@JvmSuppressWildcards ViewBinder>,
) {
- private val dialogShowAnimationDurationMs =
- resources.getInteger(R.integer.config_dialogShowAnimationDurationMs).toLong()
- private val dialogHideAnimationDurationMs =
- resources.getInteger(R.integer.config_dialogHideAnimationDurationMs).toLong()
-
fun CoroutineScope.bind(dialog: Dialog) {
val insets: MutableStateFlow<WindowInsets> =
MutableStateFlow(WindowInsets.Builder().build())
@@ -110,22 +114,35 @@ constructor(
dialog: Dialog,
visibilityModel: Flow<VolumeDialogVisibilityModel>,
) {
+ view.applyAnimationProgress(FRACTION_HIDE)
+ val animationValueHolder = FloatValueHolder(FRACTION_HIDE)
+ val animation: SpringAnimation =
+ SpringAnimation(animationValueHolder)
+ .setSpring(
+ SpringForce()
+ .setStiffness(SPRING_STIFFNESS)
+ .setDampingRatio(SPRING_DAMPING_RATIO)
+ )
+ .setMinimumVisibleChange(ANIMATION_MINIMUM_VISIBLE_CHANGE)
+ .addUpdateListener { _, value, _ -> view.applyAnimationProgress(value) }
+ var junkListener: DynamicAnimation.OnAnimationUpdateListener? = null
+
visibilityModel
.mapLatest {
when (it) {
is VolumeDialogVisibilityModel.Visible -> {
tracer.traceVisibilityEnd(it)
- view.animateShow(
- duration = dialogShowAnimationDurationMs,
- translationX = calculateTranslationX(view),
- )
+ junkListener?.let(animation::removeUpdateListener)
+ junkListener =
+ jankListenerFactory.show(view).also(animation::addUpdateListener)
+ animation.suspendAnimate(FRACTION_SHOW)
}
is VolumeDialogVisibilityModel.Dismissed -> {
tracer.traceVisibilityEnd(it)
- view.animateHide(
- duration = dialogHideAnimationDurationMs,
- translationX = calculateTranslationX(view),
- )
+ junkListener?.let(animation::removeUpdateListener)
+ junkListener =
+ jankListenerFactory.dismiss(view).also(animation::addUpdateListener)
+ animation.suspendAnimate(FRACTION_HIDE)
dialog.dismiss()
}
is VolumeDialogVisibilityModel.Invisible -> {
@@ -136,37 +153,21 @@ constructor(
.launchIn(this)
}
- private fun calculateTranslationX(view: View): Float? {
- return if (view.display.rotation == RotationPolicy.NATURAL_ROTATION) {
- if (view.isLayoutRtl) {
- -1
+ /**
+ * @param fraction in range [0, 1]. 0 corresponds to the dialog being hidden and 1 - visible.
+ */
+ private fun View.applyAnimationProgress(fraction: Float) {
+ alpha = ceil(fraction)
+ if (display.rotation == RotationPolicy.NATURAL_ROTATION) {
+ if (isLayoutRtl) {
+ -1
+ } else {
+ 1
+ } * width / 2f
} else {
- 1
- } * view.width / 2f
- } else {
- null
- }
- }
-
- private suspend fun View.animateShow(duration: Long, translationX: Float?) {
- translationX?.let { setTranslationX(translationX) }
- alpha = 0f
- animate()
- .alpha(1f)
- .translationX(0f)
- .setDuration(duration)
- .setInterpolator(SystemUIInterpolators.LogDecelerateInterpolator())
- .suspendAnimate(jankListenerFactory.show(this, duration))
- }
-
- private suspend fun View.animateHide(duration: Long, translationX: Float?) {
- val animator =
- animate()
- .alpha(0f)
- .setDuration(duration)
- .setInterpolator(SystemUIInterpolators.LogAccelerateInterpolator())
- translationX?.let { animator.translationX(it) }
- animator.suspendAnimate(jankListenerFactory.dismiss(this, duration))
+ null
+ }
+ ?.let { maxTranslationX -> translationX = lerp(maxTranslationX, 0f, fraction) }
}
private suspend fun ViewTreeObserver.listenToComputeInternalInsets() =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactory.kt
index 9fcd77716fb6..803911a9cf77 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactory.kt
@@ -17,8 +17,8 @@
package com.android.systemui.volume.dialog.ui.utils
import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
import android.view.View
+import androidx.dynamicanimation.animation.DynamicAnimation
import com.android.internal.jank.Cuj
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
@@ -30,35 +30,36 @@ class JankListenerFactory
@Inject
constructor(private val interactionJankMonitor: InteractionJankMonitor) {
- fun show(view: View, timeout: Long) = getJunkListener(view, "show", timeout)
-
- fun update(view: View, timeout: Long) = getJunkListener(view, "update", timeout)
+ fun show(view: View): DynamicAnimation.OnAnimationUpdateListener {
+ return createJunkListener(view, "show")
+ }
- fun dismiss(view: View, timeout: Long) = getJunkListener(view, "dismiss", timeout)
+ fun dismiss(view: View): DynamicAnimation.OnAnimationUpdateListener {
+ return createJunkListener(view, "dismiss")
+ }
- private fun getJunkListener(
+ private fun createJunkListener(
view: View,
type: String,
- timeout: Long,
- ): Animator.AnimatorListener {
- return object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator) {
+ ): DynamicAnimation.OnAnimationUpdateListener {
+ var trackedStart = false
+ return DynamicAnimation.OnAnimationUpdateListener { animation, _, _ ->
+ if (!trackedStart) {
+ trackedStart = true
interactionJankMonitor.begin(
InteractionJankMonitor.Configuration.Builder.withView(
Cuj.CUJ_VOLUME_CONTROL,
view,
)
.setTag(type)
- .setTimeout(timeout)
)
- }
-
- override fun onAnimationEnd(animation: Animator) {
- interactionJankMonitor.end(Cuj.CUJ_VOLUME_CONTROL)
- }
-
- override fun onAnimationCancel(animation: Animator) {
- interactionJankMonitor.cancel(Cuj.CUJ_VOLUME_CONTROL)
+ animation.addEndListener { _, canceled, _, _ ->
+ if (canceled) {
+ interactionJankMonitor.cancel(Cuj.CUJ_VOLUME_CONTROL)
+ } else {
+ interactionJankMonitor.end(Cuj.CUJ_VOLUME_CONTROL)
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
index 52a19e0903e2..31e596f0278d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
@@ -96,21 +96,23 @@ suspend fun <T> ValueAnimator.suspendAnimate(onValueChanged: (T) -> Unit) {
* Starts spring animation and suspends until it's finished. Cancels the animation if the running
* coroutine is cancelled.
*/
-suspend fun SpringAnimation.suspendAnimate(onAnimationUpdate: (Float) -> Unit) =
- suspendCancellableCoroutine { continuation ->
- val updateListener =
- DynamicAnimation.OnAnimationUpdateListener { _, value, _ -> onAnimationUpdate(value) }
- val endListener =
- DynamicAnimation.OnAnimationEndListener { _, _, _, _ -> continuation.resumeIfCan(Unit) }
- addUpdateListener(updateListener)
- addEndListener(endListener)
- animateToFinalPosition(1F)
- continuation.invokeOnCancellation {
- removeUpdateListener(updateListener)
- removeEndListener(endListener)
- cancel()
- }
+suspend fun SpringAnimation.suspendAnimate(
+ finalPosition: Float = 1f,
+ onAnimationUpdate: (Float) -> Unit = {},
+) = suspendCancellableCoroutine { continuation ->
+ val updateListener =
+ DynamicAnimation.OnAnimationUpdateListener { _, value, _ -> onAnimationUpdate(value) }
+ val endListener =
+ DynamicAnimation.OnAnimationEndListener { _, _, _, _ -> continuation.resumeIfCan(Unit) }
+ addUpdateListener(updateListener)
+ addEndListener(endListener)
+ animateToFinalPosition(finalPosition)
+ continuation.invokeOnCancellation {
+ removeUpdateListener(updateListener)
+ removeEndListener(endListener)
+ cancel()
}
+}
/**
* Starts the animation and suspends until it's finished. Cancels the animation if the running
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt
deleted file mode 100644
index 57c8fd066ea8..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt
+++ /dev/null
@@ -1,29 +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 com.android.systemui.kosmos.Kosmos
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-
-val Kosmos.communalBackActionInteractor by
- Kosmos.Fixture {
- CommunalBackActionInteractor(
- communalInteractor = communalInteractor,
- communalSceneInteractor = communalSceneInteractor,
- sceneInteractor = sceneInteractor,
- )
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 7a2b7c24252b..047bd13f0c27 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -29,6 +29,7 @@ import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToPrimaryBouncerTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dozingToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel
@@ -81,6 +82,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
aodToPrimaryBouncerTransitionViewModel = aodToPrimaryBouncerTransitionViewModel,
+ dozingToDreamingTransitionViewModel = dozingToDreamingTransitionViewModel,
dozingToGlanceableHubTransitionViewModel = dozingToGlanceableHubTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt
index da32095dce6d..386e0feb3b3a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt
@@ -16,7 +16,6 @@
package com.android.systemui.volume.dialog.ui.binder
-import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.volume.dialog.ringer.volumeDialogRingerViewBinder
import com.android.systemui.volume.dialog.settings.ui.binder.volumeDialogSettingsButtonViewBinder
@@ -28,7 +27,6 @@ import com.android.systemui.volume.dialog.utils.volumeTracer
val Kosmos.volumeDialogViewBinder by
Kosmos.Fixture {
VolumeDialogViewBinder(
- applicationContext.resources,
volumeDialogViewModel,
jankListenerFactory,
volumeTracer,
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 7e8bb28b6a37..2af74f620c95 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -569,7 +569,8 @@ public final class DreamManagerService extends SystemService {
}
private void requestDreamInternal() {
- if (isDreamingInternal() && !dreamIsFrontmost() && mController.bringDreamToFront()) {
+ if (isDreamingInternal() && !dreamIsFrontmost() && mController.bringDreamToFront()
+ && !isDozingInternal()) {
return;
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index b0ef80793cd7..9ed9b6e56f13 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -25,6 +25,8 @@ import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
+import static com.android.server.notification.Flags.FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER;
+import static com.android.server.notification.Flags.managedServicesConcurrentMultiuser;
import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled;
import android.annotation.FlaggedApi;
@@ -75,7 +77,9 @@ import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.LocalServices;
import com.android.server.notification.NotificationManagerService.DumpFilter;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
import org.xmlpull.v1.XmlPullParser;
@@ -134,6 +138,7 @@ abstract public class ManagedServices {
private final UserProfiles mUserProfiles;
protected final IPackageManager mPm;
protected final UserManager mUm;
+ protected final UserManagerInternal mUmInternal;
private final Config mConfig;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -157,12 +162,17 @@ abstract public class ManagedServices {
protected final ArraySet<String> mDefaultPackages = new ArraySet<>();
// lists the component names of all enabled (and therefore potentially connected)
- // app services for current profiles.
+ // app services for each user. This is intended to support a concurrent multi-user environment.
+ // key value is the resolved userId.
@GuardedBy("mMutex")
- private final ArraySet<ComponentName> mEnabledServicesForCurrentProfiles = new ArraySet<>();
- // Just the packages from mEnabledServicesForCurrentProfiles
+ private final SparseArray<ArraySet<ComponentName>> mEnabledServicesByUser =
+ new SparseArray<>();
+ // Just the packages from mEnabledServicesByUser
+ // This is intended to support a concurrent multi-user environment.
+ // key value is the resolved userId.
@GuardedBy("mMutex")
- private final ArraySet<String> mEnabledServicesPackageNames = new ArraySet<>();
+ private final SparseArray<ArraySet<String>> mEnabledServicesPackageNamesByUser =
+ new SparseArray<>();
// Per user id, list of enabled packages that have nevertheless asked not to be run
@GuardedBy("mSnoozing")
private final SparseSetArray<ComponentName> mSnoozing = new SparseSetArray<>();
@@ -195,6 +205,7 @@ abstract public class ManagedServices {
mConfig = getConfig();
mApprovalLevel = APPROVAL_BY_COMPONENT;
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mUmInternal = LocalServices.getService(UserManagerInternal.class);
}
abstract protected Config getConfig();
@@ -383,11 +394,30 @@ abstract public class ManagedServices {
}
synchronized (mMutex) {
- pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size()
- + ") enabled for current profiles:");
- for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
- if (filter != null && !filter.matches(cmpt)) continue;
- pw.println(" " + cmpt);
+ if (managedServicesConcurrentMultiuser()) {
+ for (int i = 0; i < mEnabledServicesByUser.size(); i++) {
+ final int userId = mEnabledServicesByUser.keyAt(i);
+ final ArraySet<ComponentName> componentNames =
+ mEnabledServicesByUser.get(userId);
+ String userString = userId == UserHandle.USER_CURRENT
+ ? "current profiles" : "user " + Integer.toString(userId);
+ pw.println(" All " + getCaption() + "s (" + componentNames.size()
+ + ") enabled for " + userString + ":");
+ for (ComponentName cmpt : componentNames) {
+ if (filter != null && !filter.matches(cmpt)) continue;
+ pw.println(" " + cmpt);
+ }
+ }
+ } else {
+ final ArraySet<ComponentName> enabledServicesForCurrentProfiles =
+ mEnabledServicesByUser.get(UserHandle.USER_CURRENT);
+ pw.println(" All " + getCaption() + "s ("
+ + enabledServicesForCurrentProfiles.size()
+ + ") enabled for current profiles:");
+ for (ComponentName cmpt : enabledServicesForCurrentProfiles) {
+ if (filter != null && !filter.matches(cmpt)) continue;
+ pw.println(" " + cmpt);
+ }
}
pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):");
@@ -442,11 +472,24 @@ abstract public class ManagedServices {
}
}
-
synchronized (mMutex) {
- for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
- if (filter != null && !filter.matches(cmpt)) continue;
- cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED);
+ if (managedServicesConcurrentMultiuser()) {
+ for (int i = 0; i < mEnabledServicesByUser.size(); i++) {
+ final int userId = mEnabledServicesByUser.keyAt(i);
+ final ArraySet<ComponentName> componentNames =
+ mEnabledServicesByUser.get(userId);
+ for (ComponentName cmpt : componentNames) {
+ if (filter != null && !filter.matches(cmpt)) continue;
+ cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED);
+ }
+ }
+ } else {
+ final ArraySet<ComponentName> enabledServicesForCurrentProfiles =
+ mEnabledServicesByUser.get(UserHandle.USER_CURRENT);
+ for (ComponentName cmpt : enabledServicesForCurrentProfiles) {
+ if (filter != null && !filter.matches(cmpt)) continue;
+ cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED);
+ }
}
for (ManagedServiceInfo info : mServices) {
if (filter != null && !filter.matches(info.component)) continue;
@@ -841,9 +884,31 @@ abstract public class ManagedServices {
}
}
+ /** convenience method for looking in mEnabledServicesPackageNamesByUser
+ * for UserHandle.USER_CURRENT.
+ * This is a legacy API. When FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER becomes
+ * trunk stable, this API should be deprecated. Additionally, when this method
+ * is deprecated, the unit tests written using this method should also be revised.
+ *
+ * @param pkg target package name
+ * @return boolean value that indicates whether it is enabled for the current profiles
+ */
protected boolean isComponentEnabledForPackage(String pkg) {
+ return isComponentEnabledForPackage(pkg, UserHandle.USER_CURRENT);
+ }
+
+ /** convenience method for looking in mEnabledServicesPackageNamesByUser
+ *
+ * @param pkg target package name
+ * @param userId the id of the target user
+ * @return boolean value that indicates whether it is enabled for the target user
+ */
+ @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ protected boolean isComponentEnabledForPackage(String pkg, int userId) {
synchronized (mMutex) {
- return mEnabledServicesPackageNames.contains(pkg);
+ ArraySet<String> enabledServicesPackageNames =
+ mEnabledServicesPackageNamesByUser.get(resolveUserId(userId));
+ return enabledServicesPackageNames != null && enabledServicesPackageNames.contains(pkg);
}
}
@@ -1016,9 +1081,14 @@ abstract public class ManagedServices {
public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) {
if (DEBUG) {
synchronized (mMutex) {
+ int resolvedUserId = (managedServicesConcurrentMultiuser()
+ && (uidList != null && uidList.length > 0))
+ ? resolveUserId(UserHandle.getUserId(uidList[0]))
+ : UserHandle.USER_CURRENT;
Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage
+ " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList))
- + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames);
+ + " mEnabledServicesPackageNames="
+ + mEnabledServicesPackageNamesByUser.get(resolvedUserId));
}
}
@@ -1034,11 +1104,18 @@ abstract public class ManagedServices {
}
}
for (String pkgName : pkgList) {
- if (isComponentEnabledForPackage(pkgName)) {
- anyServicesInvolved = true;
+ if (!managedServicesConcurrentMultiuser()) {
+ if (isComponentEnabledForPackage(pkgName)) {
+ anyServicesInvolved = true;
+ }
}
if (uidList != null && uidList.length > 0) {
for (int uid : uidList) {
+ if (managedServicesConcurrentMultiuser()) {
+ if (isComponentEnabledForPackage(pkgName, UserHandle.getUserId(uid))) {
+ anyServicesInvolved = true;
+ }
+ }
if (isPackageAllowed(pkgName, UserHandle.getUserId(uid))) {
anyServicesInvolved = true;
trimApprovedListsForInvalidServices(pkgName, UserHandle.getUserId(uid));
@@ -1065,6 +1142,36 @@ abstract public class ManagedServices {
unbindUserServices(user);
}
+ /**
+ * Call this method when a user is stopped
+ *
+ * @param user the id of the stopped user
+ */
+ public void onUserStopped(int user) {
+ if (!managedServicesConcurrentMultiuser()) {
+ return;
+ }
+ boolean hasAny = false;
+ synchronized (mMutex) {
+ if (mEnabledServicesByUser.contains(user)
+ && mEnabledServicesPackageNamesByUser.contains(user)) {
+ // Through the ManagedServices.resolveUserId,
+ // we resolve UserHandle.USER_CURRENT as the key for users
+ // other than the visible background user.
+ // Therefore, the user IDs that exist as keys for each member variable
+ // correspond to the visible background user.
+ // We need to unbind services of the stopped visible background user.
+ mEnabledServicesByUser.remove(user);
+ mEnabledServicesPackageNamesByUser.remove(user);
+ hasAny = true;
+ }
+ }
+ if (hasAny) {
+ Slog.i(TAG, "Removing approved services for stopped user " + user);
+ unbindUserServices(user);
+ }
+ }
+
public void onUserSwitched(int user) {
if (DEBUG) Slog.d(TAG, "onUserSwitched u=" + user);
unbindOtherUserServices(user);
@@ -1386,19 +1493,42 @@ abstract public class ManagedServices {
protected void populateComponentsToBind(SparseArray<Set<ComponentName>> componentsToBind,
final IntArray activeUsers,
SparseArray<ArraySet<ComponentName>> approvedComponentsByUser) {
- mEnabledServicesForCurrentProfiles.clear();
- mEnabledServicesPackageNames.clear();
final int nUserIds = activeUsers.size();
-
+ if (managedServicesConcurrentMultiuser()) {
+ for (int i = 0; i < nUserIds; ++i) {
+ final int resolvedUserId = resolveUserId(activeUsers.get(i));
+ if (mEnabledServicesByUser.get(resolvedUserId) != null) {
+ mEnabledServicesByUser.get(resolvedUserId).clear();
+ }
+ if (mEnabledServicesPackageNamesByUser.get(resolvedUserId) != null) {
+ mEnabledServicesPackageNamesByUser.get(resolvedUserId).clear();
+ }
+ }
+ } else {
+ mEnabledServicesByUser.clear();
+ mEnabledServicesPackageNamesByUser.clear();
+ }
for (int i = 0; i < nUserIds; ++i) {
- // decode the list of components
final int userId = activeUsers.get(i);
+ // decode the list of components
final ArraySet<ComponentName> userComponents = approvedComponentsByUser.get(userId);
if (null == userComponents) {
componentsToBind.put(userId, new ArraySet<>());
continue;
}
+ final int resolvedUserId = managedServicesConcurrentMultiuser()
+ ? resolveUserId(userId)
+ : UserHandle.USER_CURRENT;
+ ArraySet<ComponentName> enabledServices =
+ mEnabledServicesByUser.contains(resolvedUserId)
+ ? mEnabledServicesByUser.get(resolvedUserId)
+ : new ArraySet<>();
+ ArraySet<String> enabledServicesPackageName =
+ mEnabledServicesPackageNamesByUser.contains(resolvedUserId)
+ ? mEnabledServicesPackageNamesByUser.get(resolvedUserId)
+ : new ArraySet<>();
+
final Set<ComponentName> add = new HashSet<>(userComponents);
synchronized (mSnoozing) {
ArraySet<ComponentName> snoozed = mSnoozing.get(userId);
@@ -1409,12 +1539,12 @@ abstract public class ManagedServices {
componentsToBind.put(userId, add);
- mEnabledServicesForCurrentProfiles.addAll(userComponents);
-
+ enabledServices.addAll(userComponents);
for (int j = 0; j < userComponents.size(); j++) {
- final ComponentName component = userComponents.valueAt(j);
- mEnabledServicesPackageNames.add(component.getPackageName());
+ enabledServicesPackageName.add(userComponents.valueAt(j).getPackageName());
}
+ mEnabledServicesByUser.put(resolvedUserId, enabledServices);
+ mEnabledServicesPackageNamesByUser.put(resolvedUserId, enabledServicesPackageName);
}
}
@@ -1453,13 +1583,9 @@ abstract public class ManagedServices {
*/
protected void rebindServices(boolean forceRebind, int userToRebind) {
if (DEBUG) Slog.d(TAG, "rebindServices " + forceRebind + " " + userToRebind);
- IntArray userIds = mUserProfiles.getCurrentProfileIds();
boolean rebindAllCurrentUsers = mUserProfiles.isProfileUser(userToRebind, mContext)
&& allowRebindForParentUser();
- if (userToRebind != USER_ALL && !rebindAllCurrentUsers) {
- userIds = new IntArray(1);
- userIds.add(userToRebind);
- }
+ IntArray userIds = getUserIdsForRebindServices(userToRebind, rebindAllCurrentUsers);
final SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>();
final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>();
@@ -1483,6 +1609,23 @@ abstract public class ManagedServices {
bindToServices(componentsToBind);
}
+ private IntArray getUserIdsForRebindServices(int userToRebind, boolean rebindAllCurrentUsers) {
+ IntArray userIds = mUserProfiles.getCurrentProfileIds();
+ if (userToRebind != USER_ALL && !rebindAllCurrentUsers) {
+ userIds = new IntArray(1);
+ userIds.add(userToRebind);
+ } else if (managedServicesConcurrentMultiuser()
+ && userToRebind == USER_ALL) {
+ for (UserInfo user : mUm.getUsers()) {
+ if (mUmInternal.isVisibleBackgroundFullUser(user.id)
+ && !userIds.contains(user.id)) {
+ userIds.add(user.id);
+ }
+ }
+ }
+ return userIds;
+ }
+
/**
* Called when user switched to unbind all services from other users.
*/
@@ -1506,7 +1649,11 @@ abstract public class ManagedServices {
synchronized (mMutex) {
final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices();
for (ManagedServiceInfo info : removableBoundServices) {
- if ((allExceptUser && (info.userid != user))
+ // User switching is the event for the forground user.
+ // It should not affect the service of the visible background user.
+ if ((allExceptUser && (info.userid != user)
+ && !(managedServicesConcurrentMultiuser()
+ && info.isVisibleBackgroundUserService))
|| (!allExceptUser && (info.userid == user))) {
Set<ComponentName> toUnbind =
componentsToUnbind.get(info.userid, new ArraySet<>());
@@ -1861,6 +2008,29 @@ abstract public class ManagedServices {
}
/**
+ * This method returns the mapped id for the incoming user id
+ * If the incoming id was not the id of the visible background user, it returns USER_CURRENT.
+ * In the other cases, it returns the same value as the input.
+ *
+ * @param userId the id of the user
+ * @return the user id if it is a visible background user, otherwise
+ * {@link UserHandle#USER_CURRENT}
+ */
+ @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ @VisibleForTesting
+ public int resolveUserId(int userId) {
+ if (managedServicesConcurrentMultiuser()) {
+ if (mUmInternal.isVisibleBackgroundFullUser(userId)) {
+ // The dataset of the visible background user should be managed independently.
+ return userId;
+ }
+ }
+ // The data of current user and its profile users need to be managed
+ // in a dataset as before.
+ return UserHandle.USER_CURRENT;
+ }
+
+ /**
* Returns true if services in the parent user should be rebound
* when rebindServices is called with a profile userId.
* Must be false for NotificationAssistants.
@@ -1878,6 +2048,8 @@ abstract public class ManagedServices {
public int targetSdkVersion;
public Pair<ComponentName, Integer> mKey;
public int uid;
+ @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public boolean isVisibleBackgroundUserService;
public ManagedServiceInfo(IInterface service, ComponentName component,
int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion,
@@ -1889,6 +2061,10 @@ abstract public class ManagedServices {
this.connection = connection;
this.targetSdkVersion = targetSdkVersion;
this.uid = uid;
+ if (managedServicesConcurrentMultiuser()) {
+ this.isVisibleBackgroundUserService = LocalServices
+ .getService(UserManagerInternal.class).isVisibleBackgroundFullUser(userid);
+ }
mKey = Pair.create(component, userid);
}
@@ -1937,19 +2113,28 @@ abstract public class ManagedServices {
}
public boolean isSameUser(int userId) {
- if (!isEnabledForCurrentProfiles()) {
+ if (!isEnabledForUser()) {
return false;
}
return userId == USER_ALL || userId == this.userid;
}
public boolean enabledAndUserMatches(int nid) {
- if (!isEnabledForCurrentProfiles()) {
+ if (!isEnabledForUser()) {
return false;
}
if (this.userid == USER_ALL) return true;
if (this.isSystem) return true;
if (nid == USER_ALL || nid == this.userid) return true;
+ if (managedServicesConcurrentMultiuser()
+ && mUmInternal.getProfileParentId(nid)
+ != mUmInternal.getProfileParentId(this.userid)) {
+ // If the profile parent IDs do not match each other,
+ // it is determined that the users do not match.
+ // This situation may occur when comparing the current user's ID
+ // with the visible background user's ID.
+ return false;
+ }
return supportsProfiles()
&& mUserProfiles.isCurrentProfile(nid)
&& isPermittedForProfile(nid);
@@ -1969,12 +2154,21 @@ abstract public class ManagedServices {
removeServiceImpl(this.service, this.userid);
}
- /** convenience method for looking in mEnabledServicesForCurrentProfiles */
- public boolean isEnabledForCurrentProfiles() {
+ /**
+ * convenience method for looking in mEnabledServicesByUser.
+ * If FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER is disabled, this manages the data using
+ * only UserHandle.USER_CURRENT as the key, in order to behave the same as the legacy logic.
+ */
+ public boolean isEnabledForUser() {
if (this.isSystem) return true;
if (this.connection == null) return false;
synchronized (mMutex) {
- return mEnabledServicesForCurrentProfiles.contains(this.component);
+ int resolvedUserId = managedServicesConcurrentMultiuser()
+ ? resolveUserId(this.userid)
+ : UserHandle.USER_CURRENT;
+ ArraySet<ComponentName> enabledServices =
+ mEnabledServicesByUser.get(resolvedUserId);
+ return enabledServices != null && enabledServices.contains(this.component);
}
}
@@ -2017,10 +2211,30 @@ abstract public class ManagedServices {
}
}
- /** convenience method for looking in mEnabledServicesForCurrentProfiles */
+ /** convenience method for looking in mEnabledServicesByUser for UserHandle.USER_CURRENT.
+ * This is a legacy API. When FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER becomes
+ * trunk stable, this API should be deprecated. Additionally, when this method
+ * is deprecated, the unit tests written using this method should also be revised.
+ *
+ * @param component target component name
+ * @return boolean value that indicates whether it is enabled for the current profiles
+ */
public boolean isComponentEnabledForCurrentProfiles(ComponentName component) {
+ return isComponentEnabledForUser(component, UserHandle.USER_CURRENT);
+ }
+
+ /** convenience method for looking in mEnabledServicesForUser
+ *
+ * @param component target component name
+ * @param userId the id of the target user
+ * @return boolean value that indicates whether it is enabled for the target user
+ */
+ @FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public boolean isComponentEnabledForUser(ComponentName component, int userId) {
synchronized (mMutex) {
- return mEnabledServicesForCurrentProfiles.contains(component);
+ ArraySet<ComponentName> enabledServicesForUser =
+ mEnabledServicesByUser.get(resolveUserId(userId));
+ return enabledServicesForUser != null && enabledServicesForUser.contains(component);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3a3deb00562e..09c8b5ba823e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -173,6 +173,7 @@ import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
import static com.android.server.notification.Flags.expireBitmaps;
+import static com.android.server.notification.Flags.managedServicesConcurrentMultiuser;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
import static com.android.server.utils.PriorityDump.PRIORITY_ARG;
@@ -2323,6 +2324,9 @@ public class NotificationManagerService extends SystemService {
if (userHandle >= 0) {
cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
REASON_USER_STOPPED);
+ mConditionProviders.onUserStopped(userHandle);
+ mListeners.onUserStopped(userHandle);
+ mAssistants.onUserStopped(userHandle);
}
} else if (
isProfileUnavailable(action)) {
@@ -5244,6 +5248,21 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ @FlaggedApi(android.app.Flags.FLAG_NM_BINDER_PERF_GET_APPS_WITH_CHANNELS)
+ public List<String> getPackagesWithAnyChannels(int userId) throws RemoteException {
+ checkCallerIsSystem();
+ UserHandle user = UserHandle.of(userId);
+ List<String> packages = mPreferencesHelper.getPackagesWithAnyChannels(userId);
+ for (int i = packages.size() - 1; i >= 0; i--) {
+ String pkg = packages.get(i);
+ if (!areNotificationsEnabledForPackage(pkg, getUidForPackageAndUser(pkg, user))) {
+ packages.remove(i);
+ }
+ }
+ return packages;
+ }
+
+ @Override
public void clearData(String packageName, int uid, boolean fromApp) throws RemoteException {
boolean packagesChanged = false;
checkCallerIsSystem();
@@ -5715,12 +5734,13 @@ public class NotificationManagerService extends SystemService {
public void requestBindListener(ComponentName component) {
checkCallerIsSystemOrSameApp(component.getPackageName());
int uid = Binder.getCallingUid();
+ int userId = UserHandle.getUserId(uid);
final long identity = Binder.clearCallingIdentity();
try {
- ManagedServices manager =
- mAssistants.isComponentEnabledForCurrentProfiles(component)
- ? mAssistants
- : mListeners;
+ boolean isAssistantEnabled = managedServicesConcurrentMultiuser()
+ ? mAssistants.isComponentEnabledForUser(component, userId)
+ : mAssistants.isComponentEnabledForCurrentProfiles(component);
+ ManagedServices manager = isAssistantEnabled ? mAssistants : mListeners;
manager.setComponentState(component, UserHandle.getUserId(uid), true);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -5747,16 +5767,16 @@ public class NotificationManagerService extends SystemService {
public void requestUnbindListenerComponent(ComponentName component) {
checkCallerIsSameApp(component.getPackageName());
int uid = Binder.getCallingUid();
+ int userId = UserHandle.getUserId(uid);
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationLock) {
- ManagedServices manager =
- mAssistants.isComponentEnabledForCurrentProfiles(component)
- ? mAssistants
- : mListeners;
- if (manager.isPackageOrComponentAllowed(component.flattenToString(),
- UserHandle.getUserId(uid))) {
- manager.setComponentState(component, UserHandle.getUserId(uid), false);
+ boolean isAssistantEnabled = managedServicesConcurrentMultiuser()
+ ? mAssistants.isComponentEnabledForUser(component, userId)
+ : mAssistants.isComponentEnabledForCurrentProfiles(component);
+ ManagedServices manager = isAssistantEnabled ? mAssistants : mListeners;
+ if (manager.isPackageOrComponentAllowed(component.flattenToString(), userId)) {
+ manager.setComponentState(component, userId, false);
}
}
} finally {
@@ -6534,6 +6554,13 @@ public class NotificationManagerService extends SystemService {
} catch (NameNotFoundException e) {
return false;
}
+ if (managedServicesConcurrentMultiuser()) {
+ return checkPackagePolicyAccess(pkg)
+ || mListeners.isComponentEnabledForPackage(pkg,
+ UserHandle.getCallingUserId())
+ || (mDpm != null
+ && (mDpm.isActiveProfileOwner(uid) || mDpm.isActiveDeviceOwner(uid)));
+ }
//TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
return checkPackagePolicyAccess(pkg)
|| mListeners.isComponentEnabledForPackage(pkg)
@@ -6938,7 +6965,8 @@ public class NotificationManagerService extends SystemService {
android.Manifest.permission.INTERACT_ACROSS_USERS,
"setNotificationListenerAccessGrantedForUser for user " + userId);
}
- if (mUmInternal.isVisibleBackgroundFullUser(userId)) {
+ if (!managedServicesConcurrentMultiuser()
+ && mUmInternal.isVisibleBackgroundFullUser(userId)) {
// The main use case for visible background users is the Automotive multi-display
// configuration where a passenger can use a secondary display while the driver is
// using the main display. NotificationListeners is designed only for the current
@@ -13150,7 +13178,8 @@ public class NotificationManagerService extends SystemService {
@Override
public void onUserUnlocked(int user) {
- if (mUmInternal.isVisibleBackgroundFullUser(user)) {
+ if (!managedServicesConcurrentMultiuser()
+ && mUmInternal.isVisibleBackgroundFullUser(user)) {
// The main use case for visible background users is the Automotive
// multi-display configuration where a passenger can use a secondary
// display while the driver is using the main display.
@@ -13790,7 +13819,7 @@ public class NotificationManagerService extends SystemService {
// TODO (b/73052211): if the ranking update changed the notification type,
// cancel notifications for NLSes that can't see them anymore
for (final ManagedServiceInfo serviceInfo : getServices()) {
- if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener(
+ if (!serviceInfo.isEnabledForUser() || !isInteractionVisibleToListener(
serviceInfo, ActivityManager.getCurrentUser())) {
continue;
}
@@ -13818,7 +13847,7 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
public void notifyListenerHintsChangedLocked(final int hints) {
for (final ManagedServiceInfo serviceInfo : getServices()) {
- if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener(
+ if (!serviceInfo.isEnabledForUser() || !isInteractionVisibleToListener(
serviceInfo, ActivityManager.getCurrentUser())) {
continue;
}
@@ -13874,7 +13903,7 @@ public class NotificationManagerService extends SystemService {
public void notifyInterruptionFilterChanged(final int interruptionFilter) {
for (final ManagedServiceInfo serviceInfo : getServices()) {
- if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener(
+ if (!serviceInfo.isEnabledForUser() || !isInteractionVisibleToListener(
serviceInfo, ActivityManager.getCurrentUser())) {
continue;
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index a171ffc2ed98..3974c839fd38 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -2006,6 +2006,29 @@ public class PreferencesHelper implements RankingConfig {
}
/**
+ * Gets all apps for this user that have a nonzero number of channels. This count does not
+ * include deleted channels.
+ */
+ @FlaggedApi(android.app.Flags.FLAG_NM_BINDER_PERF_GET_APPS_WITH_CHANNELS)
+ public @NonNull List<String> getPackagesWithAnyChannels(@UserIdInt int userId) {
+ List<String> pkgs = new ArrayList<>();
+ synchronized (mLock) {
+ for (PackagePreferences p : mPackagePreferences.values()) {
+ if (UserHandle.getUserId(p.uid) != userId) {
+ continue;
+ }
+ for (NotificationChannel c : p.channels.values()) {
+ if (!c.isDeleted()) {
+ pkgs.add(p.pkg);
+ break;
+ }
+ }
+ }
+ }
+ return pkgs;
+ }
+
+ /**
* True for pre-O apps that only have the default channel, or pre O apps that have no
* channels yet. This method will create the default channel for pre-O apps that don't have it.
* Should never be true for O+ targeting apps, but that's enforced on boot/when an app
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 048f2b6b0cbc..76cd5c88b388 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -210,3 +210,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "managed_services_concurrent_multiuser"
+ namespace: "systemui"
+ description: "Enables ManagedServices to support Concurrent multi user environment"
+ bug: "380297485"
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 76c5240ab623..4153cd1be0a6 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1163,6 +1163,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
+ private boolean shouldShowHub() {
+ final boolean hubEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), Settings.Secure.GLANCEABLE_HUB_ENABLED,
+ 1, mCurrentUserId) == 1;
+
+ return mUserManagerInternal.isUserUnlocked(mCurrentUserId) && hubEnabled
+ && mDreamManagerInternal.dreamConditionActive();
+ }
+
@VisibleForTesting
void powerPress(long eventTime, int count, int displayId) {
// SideFPS still needs to know about suppressed power buttons, in case it needs to block
@@ -1261,9 +1270,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// show hub.
boolean keyguardAvailable = !mLockPatternUtils.isLockScreenDisabled(
mCurrentUserId);
- if (mUserManagerInternal.isUserUnlocked(mCurrentUserId) && hubEnabled
- && keyguardAvailable && mDreamManagerInternal.dreamConditionActive()) {
- // If the hub can be launched, send a message to keyguard.
+ if (shouldShowHub() && keyguardAvailable) {
+ // If the hub can be launched, send a message to keyguard. We do not know if
+ // the hub is already running or not, keyguard handles turning screen off if
+ // it is.
Bundle options = new Bundle();
options.putBoolean(EXTRA_TRIGGER_HUB, true);
lockNow(options);
@@ -1324,14 +1334,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
* @param isScreenOn Whether the screen is currently on.
* @param noDreamAction The action to perform if dreaming is not possible.
*/
- private void attemptToDreamFromShortPowerButtonPress(
+ private boolean attemptToDreamFromShortPowerButtonPress(
boolean isScreenOn, Runnable noDreamAction) {
if (mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_SLEEP
&& mShortPressOnPowerBehavior != SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP) {
// If the power button behavior isn't one that should be able to trigger the dream, give
// up.
noDreamAction.run();
- return;
+ return false;
}
final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
@@ -1339,7 +1349,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
Slog.d(TAG, "Can't start dreaming when attempting to dream from short power"
+ " press (isScreenOn=" + isScreenOn + ")");
noDreamAction.run();
- return;
+ return false;
}
synchronized (mLock) {
@@ -1350,6 +1360,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
dreamManagerInternal.requestDream();
+
+ return true;
}
/**
@@ -6398,6 +6410,17 @@ public class PhoneWindowManager implements WindowManagerPolicy {
event.getDisplayId(), event.getKeyCode(), "wakeUpFromWakeKey")) {
return;
}
+
+ if (!shouldShowHub()
+ && mShortPressOnPowerBehavior == SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP
+ && event.getKeyCode() == KEYCODE_POWER
+ && attemptToDreamFromShortPowerButtonPress(false, () -> {})) {
+ // In the case that we should wake to dream and successfully initiate dreaming, do not
+ // continue waking up. Doing so will exit the dream state and cause UI to react
+ // accordingly.
+ return;
+ }
+
wakeUpFromWakeKey(
event.getEventTime(),
event.getKeyCode(),
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
index 395816902592..d06827ab0529 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
@@ -273,6 +273,9 @@ final class WearableSensingManagerPerUserService
@Override
public void onError() {
+ synchronized (mLock) {
+ ensureRemoteServiceInitiated();
+ }
synchronized (mSecureChannelLock) {
if (mSecureChannel != null
&& mSecureChannel
diff --git a/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java b/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java
index a16ff51e2d20..9f14ab7a70d3 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java
@@ -156,6 +156,7 @@ final class WearableSensingSecureChannel {
new AssociationRequest.Builder()
.setDisplayName(CDM_ASSOCIATION_DISPLAY_NAME)
.setSelfManaged(true)
+ .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WEARABLE_SENSING)
.build(),
mLightWeightExecutor,
new CompanionDeviceManager.Callback() {
@@ -195,7 +196,8 @@ final class WearableSensingSecureChannel {
mCompanionDeviceManager.attachSystemDataTransport(
associationId,
new AutoCloseInputStream(mUnderlyingTransport),
- new AutoCloseOutputStream(mUnderlyingTransport));
+ new AutoCloseOutputStream(mUnderlyingTransport),
+ CompanionDeviceManager.TRANSPORT_FLAG_EXTEND_PATCH_DIFF);
}
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
index b7de74987eb8..45523268c966 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
@@ -32,8 +32,8 @@
<uses-library android:name="android.test.runner" />
</application>
- <!-- The "targetPackage" reference the instruments APK package, which is the FakeImeApk, while
- the test package is "com.android.inputmethod.imetests" (FrameworksImeTests.apk).-->
+ <!-- The "targetPackage" reference the instruments APK package, which is the SimpleTestIme.apk,
+ while the test package is "com.android.inputmethod.imetests" (FrameworksImeTests.apk).-->
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.apps.inputmethod.simpleime"
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 72e9cc566497..5d64cb638702 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -92,6 +92,9 @@ public class InputMethodServiceTest {
private static final String DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
"settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 0";
+ /** The ids of the subtypes of SimpleIme. */
+ private static final int[] SUBTYPE_IDS = new int[]{1, 2};
+
private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
private final GestureNavSwitchHelper mGestureNavSwitchHelper = new GestureNavSwitchHelper();
@@ -173,15 +176,14 @@ public class InputMethodServiceTest {
Log.i(TAG, "Click on EditText");
verifyInputViewStatus(
() -> clickOnViewCenter(mActivity.getEditText()),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
// Press home key to hide IME.
Log.i(TAG, "Press home");
if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
assertWithMessage("Home key press was handled").that(mUiDevice.pressHome()).isTrue();
+ // This doesn't call verifyInputViewStatus, as the refactor delays the events such that
+ // getCurrentInputStarted() would be false, as we would already be in launcher.
// The IME visibility is only sent at the end of the animation. Therefore, we have to
// wait until the visibility was sent to the server and the IME window hidden.
eventually(() -> assertWithMessage("IME is not shown")
@@ -190,11 +192,7 @@ public class InputMethodServiceTest {
verifyInputViewStatus(
() -> assertWithMessage("Home key press was handled")
.that(mUiDevice.pressHome()).isTrue(),
- EVENT_HIDE,
- true /* expected */,
- false /* inputViewStarted */);
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
}
}
@@ -208,26 +206,12 @@ public class InputMethodServiceTest {
// Triggers to show IME via public API.
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
// Triggers to hide IME via public API.
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_HIDE,
- true /* expected */,
- false /* inputViewStarted */);
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have to
- // wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
- }
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
}
/**
@@ -240,26 +224,12 @@ public class InputMethodServiceTest {
// Triggers to show IME via public API.
verifyInputViewStatusOnMainSync(
() -> mActivity.showImeWithWindowInsetsController(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
// Triggers to hide IME via public API.
verifyInputViewStatusOnMainSync(
() -> mActivity.hideImeWithWindowInsetsController(),
- EVENT_HIDE,
- true /* expected */,
- false /* inputViewStarted */);
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have to
- // wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
- }
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
}
/**
@@ -277,10 +247,7 @@ public class InputMethodServiceTest {
Log.i(TAG, "Call IMS#requestShowSelf(0)");
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestShowSelf(0 /* flags */),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
if (!mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
// IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect not hide (shown).
@@ -288,51 +255,31 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestHideSelf(
InputMethodManager.HIDE_IMPLICIT_ONLY),
- EVENT_HIDE,
- false /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is still shown after HIDE_IMPLICIT_ONLY")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_HIDE, false /* eventExpected */, true /* shown */,
+ "IME is still shown after HIDE_IMPLICIT_ONLY");
}
// IME request to hide itself without any flags, expect hidden.
Log.i(TAG, "Call IMS#requestHideSelf(0)");
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestHideSelf(0 /* flags */),
- EVENT_HIDE,
- true /* expected */,
- false /* inputViewStarted */);
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have to
- // wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
- }
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
if (!mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
// IME request to show itself with flag SHOW_IMPLICIT, expect shown.
Log.i(TAG, "Call IMS#requestShowSelf(InputMethodManager.SHOW_IMPLICIT)");
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown with SHOW_IMPLICIT")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */,
+ "IME is shown with SHOW_IMPLICIT");
// IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect hidden.
Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestHideSelf(
InputMethodManager.HIDE_IMPLICIT_ONLY),
- EVENT_HIDE,
- true /* expected */,
- false /* inputViewStarted */);
- assertWithMessage("IME is not shown after HIDE_IMPLICIT_ONLY")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ EVENT_HIDE, true /* eventExpected */, false /* shown */,
+ "IME is not shown after HIDE_IMPLICIT_ONLY");
}
}
@@ -424,20 +371,14 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(
InputMethodManager.SHOW_IMPLICIT)).isTrue(),
- EVENT_SHOW,
- false /* expected */,
- false /* inputViewStarted */);
- assertWithMessage("IME is not shown after SHOW_IMPLICIT")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ EVENT_SHOW, false /* eventExpected */, false /* shown */,
+ "IME is not shown after SHOW_IMPLICIT");
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */))
.isTrue(),
- EVENT_SHOW,
- false /* expected */,
- false /* inputViewStarted */);
- assertWithMessage("IME is not shown after SHOW_EXPLICIT")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ EVENT_SHOW, false /* eventExpected */, false /* shown */,
+ "IME is not shown after SHOW_EXPLICIT");
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -455,10 +396,7 @@ public class InputMethodServiceTest {
// IME should be shown.
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
}
/**
@@ -472,10 +410,7 @@ public class InputMethodServiceTest {
// the IME should be shown.
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
}
/**
@@ -492,9 +427,9 @@ public class InputMethodServiceTest {
.that(mUiDevice.isNaturalOrientation()).isFalse());
// Wait for the TestActivity to be recreated.
eventually(() -> assertWithMessage("Activity was re-created after rotation")
- .that(TestActivity.getLastCreatedInstance()).isNotEqualTo(mActivity));
+ .that(TestActivity.getInstance()).isNotEqualTo(mActivity));
// Get the new TestActivity.
- mActivity = TestActivity.getLastCreatedInstance();
+ mActivity = TestActivity.getInstance();
assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull();
// Wait for the new EditText to be served by InputMethodManager.
eventually(() -> assertWithMessage("Has an input connection to the re-created Activity")
@@ -502,10 +437,7 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
}
/**
@@ -527,9 +459,9 @@ public class InputMethodServiceTest {
.that(mUiDevice.isNaturalOrientation()).isFalse());
// Wait for the TestActivity to be recreated.
eventually(() -> assertWithMessage("Activity was re-created after rotation")
- .that(TestActivity.getLastCreatedInstance()).isNotEqualTo(mActivity));
+ .that(TestActivity.getInstance()).isNotEqualTo(mActivity));
// Get the new TestActivity.
- mActivity = TestActivity.getLastCreatedInstance();
+ mActivity = TestActivity.getInstance();
assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull();
// Wait for the new EditText to be served by InputMethodManager.
eventually(() -> assertWithMessage("Has an input connection to the re-created Activity")
@@ -537,11 +469,7 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
- EVENT_SHOW,
- false /* expected */,
- false /* inputViewStarted */);
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ EVENT_SHOW, false /* eventExpected */, false /* shown */, "IME is not shown");
}
/**
@@ -561,10 +489,7 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -594,11 +519,7 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(() ->assertThat(
mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT))
.isTrue(),
- EVENT_SHOW,
- false /* expected */,
- false /* inputViewStarted */);
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ EVENT_SHOW, false /* eventExpected */, false /* shown */, "IME is not shown");
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -623,10 +544,7 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */))
.isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
// Simulate connecting a hardware keyboard.
config.keyboard = Configuration.KEYBOARD_QWERTY;
@@ -637,11 +555,8 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.onConfigurationChanged(config),
- EVENT_CONFIG,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is still shown after a configuration change")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_CONFIG, true /* eventExpected */, true /* shown */,
+ "IME is still shown after a configuration change");
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -672,10 +587,7 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(
InputMethodManager.SHOW_IMPLICIT)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
// Simulate connecting a hardware keyboard.
config.keyboard = Configuration.KEYBOARD_QWERTY;
@@ -692,11 +604,8 @@ public class InputMethodServiceTest {
// still alive.
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.onConfigurationChanged(config),
- EVENT_CONFIG,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is not shown after a configuration change")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ EVENT_CONFIG, true /* eventExpected */, true /* inputViewStarted */,
+ false /* shown */, "IME is not shown after a configuration change");
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -722,31 +631,21 @@ public class InputMethodServiceTest {
// Explicit show request.
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
// Implicit show request.
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(
InputMethodManager.SHOW_IMPLICIT)).isTrue(),
- EVENT_SHOW,
- false /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is still shown")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, false /* eventExpected */, true /* shown */, "IME is still shown");
// Simulate a fake configuration change to avoid the recreation of TestActivity.
// This should now consider the implicit show request, but keep the state from the
// explicit show request, and thus not hide the IME.
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.onConfigurationChanged(config),
- EVENT_CONFIG,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is still shown after a configuration change")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_CONFIG, true /* eventExpected */, true /* shown */,
+ "IME is still shown after a configuration change");
} finally {
mInputMethodService.getResources()
.updateConfiguration(initialConfig, null /* metrics */, null /* compat */);
@@ -769,27 +668,17 @@ public class InputMethodServiceTest {
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_FORCED)).isTrue(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
- EVENT_SHOW,
- false /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is still shown")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, false /* eventExpected */, true /* shown */, "IME is still shown");
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS))
.isTrue(),
- EVENT_HIDE,
- false /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is still shown after HIDE_NOT_ALWAYS")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_HIDE, false /* eventExpected */, true /* shown */,
+ "IME is still shown after HIDE_NOT_ALWAYS");
}
/**
@@ -800,18 +689,15 @@ public class InputMethodServiceTest {
setShowImeWithHardKeyboard(true /* enabled */);
Log.i(TAG, "Set orientation natural");
- verifyFullscreenMode(() -> setOrientation(0),
- false /* expected */,
+ verifyFullscreenMode(() -> setOrientation(0), false /* eventExpected */,
true /* orientationPortrait */);
Log.i(TAG, "Set orientation left");
- verifyFullscreenMode(() -> setOrientation(1),
- true /* expected */,
+ verifyFullscreenMode(() -> setOrientation(1), true /* eventExpected */,
false /* orientationPortrait */);
Log.i(TAG, "Set orientation right");
- verifyFullscreenMode(() -> setOrientation(2),
- false /* expected */,
+ verifyFullscreenMode(() -> setOrientation(2), false /* eventExpected */,
false /* orientationPortrait */);
}
@@ -845,10 +731,7 @@ public class InputMethodServiceTest {
setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
mActivity.showImeWithWindowInsetsController();
},
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
assertWithMessage("IME navigation bar is initially shown")
.that(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue();
@@ -883,10 +766,7 @@ public class InputMethodServiceTest {
setDrawsImeNavBarAndSwitcherButton(false /* enabled */);
mActivity.showImeWithWindowInsetsController();
},
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
assertWithMessage("IME navigation bar is initially not shown")
.that(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse();
@@ -917,24 +797,15 @@ public class InputMethodServiceTest {
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
() -> mActivity.showImeWithWindowInsetsController(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
final var backButton = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
- backButton.click();
- mInstrumentation.waitForIdleSync();
-
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have
- // to wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
- }
+ verifyInputViewStatus(
+ () -> {
+ backButton.click();
+ mInstrumentation.waitForIdleSync();
+ },
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
}
}
@@ -952,30 +823,20 @@ public class InputMethodServiceTest {
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
() -> mActivity.showImeWithWindowInsetsController(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
final var backButton = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
- backButton.longClick();
- mInstrumentation.waitForIdleSync();
-
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have
- // to wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
- }
+ verifyInputViewStatus(
+ () -> {
+ backButton.longClick();
+ mInstrumentation.waitForIdleSync();
+ },
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
}
}
/**
- * Verifies that clicking on the IME switch button either shows the Input Method Switcher Menu,
- * or switches the input method.
+ * Verifies that clicking on the IME switch button switches the input method subtype.
*/
@Test
public void testImeSwitchButtonClick() throws Exception {
@@ -985,34 +846,32 @@ public class InputMethodServiceTest {
setShowImeWithHardKeyboard(true /* enabled */);
+ final var info = mImm.getCurrentInputMethodInfo();
+ assertWithMessage("InputMethodInfo is not null").that(info).isNotNull();
+ mImm.setExplicitlyEnabledInputMethodSubtypes(info.getId(), SUBTYPE_IDS);
+
+ final var initialSubtype = mImm.getCurrentInputMethodSubtype();
+
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
() -> {
setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
mActivity.showImeWithWindowInsetsController();
},
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
-
- final var initialInfo = mImm.getCurrentInputMethodInfo();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
final var imeSwitcherButton = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
imeSwitcherButton.click();
mInstrumentation.waitForIdleSync();
- final var newInfo = mImm.getCurrentInputMethodInfo();
+ final var newSubtype = mImm.getCurrentInputMethodSubtype();
- assertWithMessage("Input Method Switcher Menu is shown or input method was switched")
- .that(isInputMethodPickerShown(mImm) || !Objects.equals(initialInfo, newInfo))
+ assertWithMessage("Input method subtype was switched")
+ .that(!Objects.equals(initialSubtype, newSubtype))
.isTrue();
assertWithMessage("IME is still shown after IME Switcher button was clicked")
.that(mInputMethodService.isInputViewShown()).isTrue();
-
- // Hide the IME Switcher Menu before finishing.
- mUiDevice.pressBack();
}
}
@@ -1027,16 +886,17 @@ public class InputMethodServiceTest {
setShowImeWithHardKeyboard(true /* enabled */);
+ final var info = mImm.getCurrentInputMethodInfo();
+ assertWithMessage("InputMethodInfo is not null").that(info).isNotNull();
+ mImm.setExplicitlyEnabledInputMethodSubtypes(info.getId(), SUBTYPE_IDS);
+
try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
verifyInputViewStatusOnMainSync(
() -> {
setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
mActivity.showImeWithWindowInsetsController();
},
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
final var imeSwitcherButton = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
imeSwitcherButton.longClick();
@@ -1052,58 +912,78 @@ public class InputMethodServiceTest {
}
}
- private void verifyInputViewStatus(@NonNull Runnable runnable, @Event int event,
- boolean expected, boolean inputViewStarted) {
- verifyInputViewStatusInternal(runnable, event, expected, inputViewStarted,
- false /* runOnMainSync */);
+ private void verifyInputViewStatus(@NonNull Runnable runnable, @Event int eventType,
+ boolean eventExpected, boolean shown, @NonNull String message) {
+ verifyInputViewStatusInternal(runnable, eventType, eventExpected,
+ shown /* inputViewStarted */, shown, message, false /* runOnMainSync */);
+ }
+
+ private void verifyInputViewStatusOnMainSync(@NonNull Runnable runnable, @Event int eventType,
+ boolean eventExpected, boolean shown, @NonNull String message) {
+ verifyInputViewStatusInternal(runnable, eventType, eventExpected,
+ shown /* inputViewStarted */, shown, message, true /* runOnMainSync */);
}
- private void verifyInputViewStatusOnMainSync(@NonNull Runnable runnable, @Event int event,
- boolean expected, boolean inputViewStarted) {
- verifyInputViewStatusInternal(runnable, event, expected, inputViewStarted,
- true /* runOnMainSync */);
+ private void verifyInputViewStatusOnMainSync(@NonNull Runnable runnable, @Event int eventType,
+ boolean eventExpected, boolean inputViewStarted, boolean shown,
+ @NonNull String message) {
+ verifyInputViewStatusInternal(runnable, eventType, eventExpected, inputViewStarted, shown,
+ message, true /* runOnMainSync */);
}
/**
* Verifies the status of the Input View after executing the given runnable, and waiting that
- * the event was either triggered or not, based on the given expectation.
+ * the event was either called or not.
*
- * @param runnable the runnable to trigger the event
- * @param event the event to await.
- * @param expected whether the event is expected to be triggered.
- * @param inputViewStarted the expected state of the Input View after executing the runnable.
+ * @param runnable the runnable to call the event.
+ * @param eventType the event type to wait for.
+ * @param eventExpected whether the event is expected to be called.
+ * @param inputViewStarted whether the input view is expected to be started.
+ * @param shown whether the input view is expected to be shown.
+ * @param message the message for the input view shown assertion.
* @param runOnMainSync whether to execute the runnable on the main thread.
*/
- private void verifyInputViewStatusInternal(@NonNull Runnable runnable, @Event int event,
- boolean expected, boolean inputViewStarted, boolean runOnMainSync) {
- final boolean completed;
+ private void verifyInputViewStatusInternal(@NonNull Runnable runnable, @Event int eventType,
+ boolean eventExpected, boolean inputViewStarted, boolean shown, @NonNull String message,
+ boolean runOnMainSync) {
+ final boolean eventCalled;
try {
final var latch = new CountDownLatch(1);
- mInputMethodService.setCountDownLatchForTesting(latch, event);
+ mInputMethodService.setCountDownLatchForTesting(latch, eventType);
if (runOnMainSync) {
mInstrumentation.runOnMainSync(runnable);
} else {
runnable.run();
}
mInstrumentation.waitForIdleSync();
- completed = latch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+ eventCalled = latch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
} catch (InterruptedException e) {
fail("Interrupted while waiting for latch: " + e.getMessage());
return;
} finally {
- mInputMethodService.setCountDownLatchForTesting(null /* latch */, event);
+ mInputMethodService.setCountDownLatchForTesting(null /* latch */, eventType);
}
- if (expected && !completed) {
- fail("Timed out waiting for " + eventToString(event));
- } else if (!expected && completed) {
- fail("Unexpected call " + eventToString(event));
+ if (eventExpected && !eventCalled) {
+ fail("Timed out waiting for " + eventToString(eventType));
+ } else if (!eventExpected && eventCalled) {
+ fail("Unexpected call " + eventToString(eventType));
}
- // Input is not finished.
+ // Input connection is not finished.
assertWithMessage("Input connection is still started")
.that(mInputMethodService.getCurrentInputStarted()).isTrue();
- assertWithMessage("IME visibility matches expected")
+ assertWithMessage("Input view started matches expected")
.that(mInputMethodService.getCurrentInputViewStarted()).isEqualTo(inputViewStarted);
+
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ // The IME visibility is only sent at the end of the hide animation. Therefore, we have
+ // to wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertWithMessage(message).that(mInputMethodService.isInputViewShown())
+ .isEqualTo(shown));
+ } else {
+ assertWithMessage(message).that(mInputMethodService.isInputViewShown())
+ .isEqualTo(shown);
+ }
}
private void setOrientation(int orientation) {
@@ -1127,31 +1007,28 @@ public class InputMethodServiceTest {
/**
* Verifies the IME fullscreen mode state after executing the given runnable.
*
- * @param runnable the runnable to execute for setting the orientation.
- * @param expected whether the runnable is expected to trigger the signal.
+ * @param runnable the runnable to set the orientation.
+ * @param eventExpected whether the event is expected to be called.
* @param orientationPortrait whether the orientation is expected to be portrait.
*/
- private void verifyFullscreenMode(@NonNull Runnable runnable, boolean expected,
+ private void verifyFullscreenMode(@NonNull Runnable runnable, boolean eventExpected,
boolean orientationPortrait) {
- verifyInputViewStatus(runnable, EVENT_CONFIG, expected, false /* inputViewStarted */);
- if (expected) {
+ verifyInputViewStatus(runnable, EVENT_CONFIG, eventExpected, false /* shown */,
+ "IME is not shown");
+ if (eventExpected) {
// Wait for the TestActivity to be recreated.
eventually(() -> assertWithMessage("Activity was re-created after rotation")
- .that(TestActivity.getLastCreatedInstance()).isNotEqualTo(mActivity));
+ .that(TestActivity.getInstance()).isNotEqualTo(mActivity));
// Get the new TestActivity.
- mActivity = TestActivity.getLastCreatedInstance();
+ mActivity = TestActivity.getInstance();
assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull();
// Wait for the new EditText to be served by InputMethodManager.
eventually(() -> assertWithMessage("Has an input connection to the re-created Activity")
.that(mImm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
}
- verifyInputViewStatusOnMainSync(
- () -> mActivity.showImeWithWindowInsetsController(),
- EVENT_SHOW,
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ verifyInputViewStatusOnMainSync(() -> mActivity.showImeWithWindowInsetsController(),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
assertWithMessage("IME orientation matches expected")
.that(mInputMethodService.getResources().getConfiguration().orientation)
@@ -1172,21 +1049,8 @@ public class InputMethodServiceTest {
.that(mInputMethodService.isFullscreenMode()).isEqualTo(!orientationPortrait);
// Hide IME before finishing the run.
- verifyInputViewStatusOnMainSync(
- () -> mActivity.hideImeWithWindowInsetsController(),
- EVENT_HIDE,
- true /* expected */,
- false /* inputViewStarted */);
-
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have to
- // wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
- }
+ verifyInputViewStatusOnMainSync(() -> mActivity.hideImeWithWindowInsetsController(),
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
}
private void prepareIme() {
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml
index 872b0688814a..3dd4a97f464f 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml
@@ -17,7 +17,14 @@
<input-method xmlns:android="http://schemas.android.com/apk/res/android">
<subtype
- android:label="FakeIme"
+ android:label="SimpleIme"
android:imeSubtypeLocale="en_US"
- android:imeSubtypeMode="keyboard"/>
+ android:imeSubtypeMode="keyboard"
+ android:subtypeId="1" />
+
+ <subtype
+ android:label="SimpleIme French"
+ android:imeSubtypeLocale="fr_FR"
+ android:imeSubtypeMode="keyboard"
+ android:subtypeId="2" />
</input-method> \ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboardView.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboardView.java
index 55d1b451c6b0..1840cdea4c20 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboardView.java
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboardView.java
@@ -23,6 +23,7 @@ import android.util.Log;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -107,6 +108,15 @@ final class SimpleKeyboardView extends FrameLayout {
mapSoftKeys();
}
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ // Handle edge to edge for navigationBars insets (system nav bar)
+ // and captionBars insets (IME navigation bar).
+ final int insetBottom = insets.getInsets(WindowInsets.Type.systemBars()).bottom;
+ setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), insetBottom);
+ return insets.inset(0, 0, 0, insetBottom);
+ }
+
/**
* Sets the key press listener.
*
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
index 3a7abbb167ec..558d1a7c4e8b 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
@@ -27,6 +27,7 @@ import androidx.annotation.Nullable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.concurrent.CountDownLatch;
/** Wrapper of {@link InputMethodService} to expose interfaces for testing purpose. */
@@ -35,8 +36,8 @@ public class InputMethodServiceWrapper extends InputMethodService {
private static final String TAG = "InputMethodServiceWrapper";
/** Last created instance of this wrapper. */
- @Nullable
- private static InputMethodServiceWrapper sInstance;
+ @NonNull
+ private static WeakReference<InputMethodServiceWrapper> sInstance = new WeakReference<>(null);
/** IME show event ({@link #onStartInputView}). */
public static final int EVENT_SHOW = 0;
@@ -68,32 +69,11 @@ public class InputMethodServiceWrapper extends InputMethodService {
@Nullable
private CountDownLatch mCountDownLatch;
- /** Gets the last created instance of this wrapper, if available. */
- @Nullable
- public static InputMethodServiceWrapper getInstance() {
- return sInstance;
- }
-
- public boolean getCurrentInputViewStarted() {
- return mInputViewStarted;
- }
-
- /**
- * Sets the latch used to wait for the IME event.
- *
- * @param latch the latch to wait on.
- * @param latchEvent the event to set the latch on.
- */
- public void setCountDownLatchForTesting(@Nullable CountDownLatch latch, @Event int latchEvent) {
- mCountDownLatch = latch;
- mLatchEvent = latchEvent;
- }
-
@Override
public void onCreate() {
Log.i(TAG, "onCreate()");
super.onCreate();
- sInstance = this;
+ sInstance = new WeakReference<>(this);
}
@Override
@@ -103,6 +83,12 @@ public class InputMethodServiceWrapper extends InputMethodService {
}
@Override
+ public void onFinishInput() {
+ Log.i(TAG, "onFinishInput()");
+ super.onFinishInput();
+ }
+
+ @Override
public void onStartInputView(EditorInfo info, boolean restarting) {
Log.i(TAG, "onStartInputView() editor=" + dumpEditorInfo(info)
+ ", restarting=" + restarting);
@@ -114,12 +100,6 @@ public class InputMethodServiceWrapper extends InputMethodService {
}
@Override
- public void onFinishInput() {
- Log.i(TAG, "onFinishInput()");
- super.onFinishInput();
- }
-
- @Override
public void onFinishInputView(boolean finishingInput) {
Log.i(TAG, "onFinishInputView()");
super.onFinishInputView(finishingInput);
@@ -146,14 +126,35 @@ public class InputMethodServiceWrapper extends InputMethodService {
}
}
+ public boolean getCurrentInputViewStarted() {
+ return mInputViewStarted;
+ }
+
+ /**
+ * Sets the latch used to wait for the IME event.
+ *
+ * @param latch the latch to wait on.
+ * @param latchEvent the event to set the latch on.
+ */
+ public void setCountDownLatchForTesting(@Nullable CountDownLatch latch, @Event int latchEvent) {
+ mCountDownLatch = latch;
+ mLatchEvent = latchEvent;
+ }
+
+ /** Gets the last created instance of this wrapper, if available. */
+ @Nullable
+ public static InputMethodServiceWrapper getInstance() {
+ return sInstance.get();
+ }
+
/**
* Gets the string representation of the IME event that is being waited on.
*
- * @param event the IME event.
+ * @param eventType the IME event type.
*/
@NonNull
- public static String eventToString(@Event int event) {
- return switch (event) {
+ public static String eventToString(@Event int eventType) {
+ return switch (eventType) {
case EVENT_SHOW -> "onStartInputView";
case EVENT_HIDE -> "onFinishInputView";
case EVENT_CONFIG -> "onConfigurationChanged";
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java
index eadbac3ca74b..ad90b32848b0 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java
@@ -45,30 +45,13 @@ import java.lang.ref.WeakReference;
public final class TestActivity extends Activity {
private static final String TAG = "TestActivity";
- private static WeakReference<TestActivity> sLastCreatedInstance = new WeakReference<>(null);
- /**
- * Start a new test activity with an editor and wait for it to begin running before returning.
- *
- * @param instrumentation application instrumentation
- * @return the newly started activity
- */
+ /** Last created instance of this activity. */
@NonNull
- public static TestActivity startSync(@NonNull Instrumentation instrumentation) {
- final var intent = new Intent()
- .setAction(Intent.ACTION_MAIN)
- .setClass(instrumentation.getTargetContext(), TestActivity.class)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- return (TestActivity) instrumentation.startActivitySync(intent);
- }
+ private static WeakReference<TestActivity> sInstance = new WeakReference<>(null);
private EditText mEditText;
- public EditText getEditText() {
- return mEditText;
- }
-
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -80,13 +63,11 @@ public final class TestActivity extends Activity {
rootView.setFitsSystemWindows(true);
setContentView(rootView);
mEditText.requestFocus();
- sLastCreatedInstance = new WeakReference<>(this);
+ sInstance = new WeakReference<>(this);
}
- /** Get the last created TestActivity instance, if available. */
- @Nullable
- public static TestActivity getLastCreatedInstance() {
- return sLastCreatedInstance.get();
+ public EditText getEditText() {
+ return mEditText;
}
/** Shows soft keyboard via InputMethodManager. */
@@ -118,4 +99,26 @@ public final class TestActivity extends Activity {
controller.hide(WindowInsets.Type.ime());
Log.i(TAG, "hideIme() via WindowInsetsController");
}
+
+ /** Gets the last created instance of this activity, if available. */
+ @Nullable
+ public static TestActivity getInstance() {
+ return sInstance.get();
+ }
+
+ /**
+ * Start a new test activity with an editor and wait for it to begin running before returning.
+ *
+ * @param instrumentation application instrumentation.
+ * @return the newly started activity.
+ */
+ @NonNull
+ public static TestActivity startSync(@NonNull Instrumentation instrumentation) {
+ final var intent = new Intent()
+ .setAction(Intent.ACTION_MAIN)
+ .setClass(instrumentation.getTargetContext(), TestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ return (TestActivity) instrumentation.startActivitySync(intent);
+ }
}
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 0eb20eb22380..66d7611a29c6 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -32,6 +32,7 @@ android_test {
"androidx.test.rules",
"hamcrest-library",
"mockito-target-inline-minus-junit4",
+ "mockito-target-extended",
"platform-compat-test-rules",
"platform-test-annotations",
"platformprotosnano",
diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
index b3ec2153542a..c9d5241c57b7 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
@@ -30,6 +30,7 @@ import android.testing.TestableContext;
import androidx.test.InstrumentationRegistry;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import org.junit.After;
@@ -41,6 +42,7 @@ import org.mockito.MockitoAnnotations;
public class UiServiceTestCase {
@Mock protected PackageManagerInternal mPmi;
+ @Mock protected UserManagerInternal mUmi;
@Mock protected UriGrantsManagerInternal mUgmInternal;
protected static final String PKG_N_MR1 = "com.example.n_mr1";
@@ -92,6 +94,8 @@ public class UiServiceTestCase {
}
});
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ LocalServices.addService(UserManagerInternal.class, mUmi);
LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
when(mUgmInternal.checkGrantUriPermission(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index e5c42082ab97..98440ecdad82 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -17,12 +17,17 @@ package com.android.server.notification;
import static android.content.Context.DEVICE_POLICY_SERVICE;
import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
+import static android.os.UserHandle.USER_ALL;
+import static android.os.UserHandle.USER_CURRENT;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.notification.Flags.FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER;
+import static com.android.server.notification.Flags.managedServicesConcurrentMultiuser;
import static com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT;
import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE;
import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled;
@@ -66,7 +71,9 @@ import android.os.IInterface;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -83,6 +90,7 @@ import com.android.server.UiServiceTestCase;
import com.google.android.collect.Lists;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -105,6 +113,9 @@ import java.util.concurrent.CountDownLatch;
public class ManagedServicesTest extends UiServiceTestCase {
+ @Rule
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private IPackageManager mIpm;
@Mock
@@ -155,6 +166,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
users.add(new UserInfo(11, "11", 0));
users.add(new UserInfo(12, "12", 0));
users.add(new UserInfo(13, "13", 0));
+ users.add(new UserInfo(99, "99", 0));
for (UserInfo user : users) {
when(mUm.getUserInfo(eq(user.id))).thenReturn(user);
}
@@ -804,6 +816,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void rebindServices_onlyBindsExactMatchesIfComponent() throws Exception {
// If the primary and secondary lists contain component names, only those components within
// the package should be matched
@@ -841,6 +854,45 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void rebindServices_onlyBindsExactMatchesIfComponent_concurrent_multiUser()
+ throws Exception {
+ // If the primary and secondary lists contain component names, only those components within
+ // the package should be matched
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm,
+ ManagedServices.APPROVAL_BY_COMPONENT);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ packages.add("anotherPackage");
+ addExpectedServices(service, packages, 0);
+
+ // only 2 components are approved per package
+ mExpectedPrimaryComponentNames.clear();
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+ mExpectedSecondaryComponentNames.clear();
+ mExpectedSecondaryComponentNames.put(0, "anotherPackage/C1:anotherPackage/C2");
+
+ loadXml(service);
+ // verify the 2 components per package are enabled (bound)
+ verifyExpectedBoundEntries(service, true, 0);
+ verifyExpectedBoundEntries(service, false, 0);
+
+ // verify the last component per package is not enabled/we don't try to bind to it
+ for (String pkg : packages) {
+ ComponentName unapprovedAdditionalComponent =
+ ComponentName.unflattenFromString(pkg + "/C3");
+ assertFalse(
+ service.isComponentEnabledForUser(
+ unapprovedAdditionalComponent, 0));
+ verify(mIpm, never()).getServiceInfo(
+ eq(unapprovedAdditionalComponent), anyLong(), anyInt());
+ }
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void rebindServices_bindsEverythingInAPackage() throws Exception {
// If the primary and secondary lists contain packages, all components within those packages
// should be bound
@@ -866,6 +918,32 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void rebindServices_bindsEverythingInAPackage_concurrent_multiUser() throws Exception {
+ // If the primary and secondary lists contain packages, all components within those packages
+ // should be bound
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_PACKAGE);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ packages.add("packagea");
+ addExpectedServices(service, packages, 0);
+
+ // 2 approved packages
+ mExpectedPrimaryPackages.clear();
+ mExpectedPrimaryPackages.put(0, "package");
+ mExpectedSecondaryPackages.clear();
+ mExpectedSecondaryPackages.put(0, "packagea");
+
+ loadXml(service);
+
+ // verify the 3 components per package are enabled (bound)
+ verifyExpectedBoundEntries(service, true, 0);
+ verifyExpectedBoundEntries(service, false, 0);
+ }
+
+ @Test
public void reregisterService_checksAppIsApproved_pkg() throws Exception {
Context context = mock(Context.class);
PackageManager pm = mock(PackageManager.class);
@@ -1118,6 +1196,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void testUpgradeAppBindsNewServices() throws Exception {
// If the primary and secondary lists contain component names, only those components within
// the package should be matched
@@ -1159,6 +1238,49 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testUpgradeAppBindsNewServices_concurrent_multiUser() throws Exception {
+ // If the primary and secondary lists contain component names, only those components within
+ // the package should be matched
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm,
+ ManagedServices.APPROVAL_BY_PACKAGE);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ addExpectedServices(service, packages, 0);
+
+ // only 2 components are approved per package
+ mExpectedPrimaryComponentNames.clear();
+ mExpectedPrimaryPackages.clear();
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+ mExpectedSecondaryComponentNames.clear();
+ mExpectedSecondaryPackages.clear();
+
+ loadXml(service);
+
+ // new component expected
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2:package/C3");
+
+ service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
+
+ // verify the 3 components per package are enabled (bound)
+ verifyExpectedBoundEntries(service, true, 0);
+
+ // verify the last component per package is not enabled/we don't try to bind to it
+ for (String pkg : packages) {
+ ComponentName unapprovedAdditionalComponent =
+ ComponentName.unflattenFromString(pkg + "/C3");
+ assertFalse(
+ service.isComponentEnabledForUser(
+ unapprovedAdditionalComponent, 0));
+ verify(mIpm, never()).getServiceInfo(
+ eq(unapprovedAdditionalComponent), anyLong(), anyInt());
+ }
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void testUpgradeAppNoPermissionNoRebind() throws Exception {
Context context = spy(getContext());
doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
@@ -1211,6 +1333,59 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testUpgradeAppNoPermissionNoRebind_concurrent_multiUser() throws Exception {
+ Context context = spy(getContext());
+ doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
+ mIpm,
+ APPROVAL_BY_COMPONENT);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ addExpectedServices(service, packages, 0);
+
+ final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
+ final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
+
+ // Both components are approved initially
+ mExpectedPrimaryComponentNames.clear();
+ mExpectedPrimaryPackages.clear();
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+ mExpectedSecondaryComponentNames.clear();
+ mExpectedSecondaryPackages.clear();
+
+ loadXml(service);
+
+ //Component package/C1 loses bind permission
+ when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
+ (Answer<ServiceInfo>) invocation -> {
+ ComponentName invocationCn = invocation.getArgument(0);
+ if (invocationCn != null) {
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = invocationCn.getPackageName();
+ serviceInfo.name = invocationCn.getClassName();
+ if (invocationCn.equals(unapprovedComponent)) {
+ serviceInfo.permission = "none";
+ } else {
+ serviceInfo.permission = service.getConfig().bindPermission;
+ }
+ serviceInfo.metaData = null;
+ return serviceInfo;
+ }
+ return null;
+ }
+ );
+
+ // Trigger package update
+ service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
+
+ assertFalse(service.isComponentEnabledForUser(unapprovedComponent, 0));
+ assertTrue(service.isComponentEnabledForUser(approvedComponent, 0));
+ }
+
+ @Test
public void testSetPackageOrComponentEnabled() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1517,6 +1692,201 @@ public class ManagedServicesTest extends UiServiceTestCase {
assertTrue(componentsToBind.get(10).contains(ComponentName.unflattenFromString("c/c")));
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testPopulateComponentsToBindWithNonProfileUser() {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ spyOn(service);
+
+ SparseArray<ArraySet<ComponentName>> approvedComponentsByUser = new SparseArray<>();
+ ArraySet<ComponentName> allowed0 = new ArraySet<>();
+ allowed0.add(ComponentName.unflattenFromString("a/a"));
+ approvedComponentsByUser.put(0, allowed0);
+ ArraySet<ComponentName> allowed10 = new ArraySet<>();
+ allowed10.add(ComponentName.unflattenFromString("b/b"));
+ approvedComponentsByUser.put(10, allowed10);
+
+ int nonProfileUser = 99;
+ ArraySet<ComponentName> allowedForNonProfileUser = new ArraySet<>();
+ allowedForNonProfileUser.add(ComponentName.unflattenFromString("c/c"));
+ approvedComponentsByUser.put(nonProfileUser, allowedForNonProfileUser);
+
+ IntArray users = new IntArray();
+ users.add(nonProfileUser);
+ users.add(10);
+ users.add(0);
+
+ SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>();
+ spyOn(service.mUmInternal);
+ when(service.mUmInternal.isVisibleBackgroundFullUser(nonProfileUser)).thenReturn(true);
+
+ service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser);
+
+ assertTrue(service.isComponentEnabledForUser(
+ ComponentName.unflattenFromString("a/a"), 0));
+ assertTrue(service.isComponentEnabledForPackage("a", 0));
+ assertTrue(service.isComponentEnabledForUser(
+ ComponentName.unflattenFromString("b/b"), 10));
+ assertTrue(service.isComponentEnabledForPackage("b", 0));
+ assertTrue(service.isComponentEnabledForPackage("b", 10));
+ assertTrue(service.isComponentEnabledForUser(
+ ComponentName.unflattenFromString("c/c"), nonProfileUser));
+ assertTrue(service.isComponentEnabledForPackage("c", nonProfileUser));
+ }
+
+
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testRebindService_profileUser() throws Exception {
+ final int profileUserId = 10;
+ when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true);
+ spyOn(mService);
+ ArgumentCaptor<IntArray> captor = ArgumentCaptor.forClass(
+ IntArray.class);
+ when(mService.allowRebindForParentUser()).thenReturn(true);
+
+ mService.rebindServices(false, profileUserId);
+
+ verify(mService).populateComponentsToBind(any(), captor.capture(), any());
+ assertTrue(captor.getValue().contains(0));
+ assertTrue(captor.getValue().contains(profileUserId));
+ }
+
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testRebindService_nonProfileUser() throws Exception {
+ final int userId = 99;
+ when(mUserProfiles.isProfileUser(userId, mContext)).thenReturn(false);
+ spyOn(mService);
+ ArgumentCaptor<IntArray> captor = ArgumentCaptor.forClass(
+ IntArray.class);
+ when(mService.allowRebindForParentUser()).thenReturn(true);
+
+ mService.rebindServices(false, userId);
+
+ verify(mService).populateComponentsToBind(any(), captor.capture(), any());
+ assertFalse(captor.getValue().contains(0));
+ assertTrue(captor.getValue().contains(userId));
+ }
+
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testRebindService_userAll() throws Exception {
+ final int userId = 99;
+ spyOn(mService);
+ spyOn(mService.mUmInternal);
+ when(mService.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(true);
+ ArgumentCaptor<IntArray> captor = ArgumentCaptor.forClass(
+ IntArray.class);
+ when(mService.allowRebindForParentUser()).thenReturn(true);
+
+ mService.rebindServices(false, USER_ALL);
+
+ verify(mService).populateComponentsToBind(any(), captor.capture(), any());
+ assertTrue(captor.getValue().contains(0));
+ assertTrue(captor.getValue().contains(userId));
+ }
+
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testOnUserStoppedWithVisibleBackgroundUser() throws Exception {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ spyOn(service);
+ int userId = 99;
+ SparseArray<ArraySet<ComponentName>> approvedComponentsByUser = new SparseArray<>();
+ ArraySet<ComponentName> allowedForNonProfileUser = new ArraySet<>();
+ allowedForNonProfileUser.add(ComponentName.unflattenFromString("a/a"));
+ approvedComponentsByUser.put(userId, allowedForNonProfileUser);
+ IntArray users = new IntArray();
+ users.add(userId);
+ SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>();
+ spyOn(service.mUmInternal);
+ when(service.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(true);
+ service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser);
+ assertTrue(service.isComponentEnabledForUser(
+ ComponentName.unflattenFromString("a/a"), userId));
+ assertTrue(service.isComponentEnabledForPackage("a", userId));
+
+ service.onUserStopped(userId);
+
+ assertFalse(service.isComponentEnabledForUser(
+ ComponentName.unflattenFromString("a/a"), userId));
+ assertFalse(service.isComponentEnabledForPackage("a", userId));
+ verify(service).unbindUserServices(eq(userId));
+ }
+
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testUnbindServicesImpl_serviceOfForegroundUser() throws Exception {
+ int switchingUserId = 10;
+ int userId = 99;
+
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ spyOn(service);
+ spyOn(service.mUmInternal);
+ when(service.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(false);
+
+ IInterface iInterface = mock(IInterface.class);
+ when(iInterface.asBinder()).thenReturn(mock(IBinder.class));
+
+ ManagedServices.ManagedServiceInfo serviceInfo = service.new ManagedServiceInfo(
+ iInterface, ComponentName.unflattenFromString("a/a"), userId, false,
+ mock(ServiceConnection.class), 26, 34);
+
+ Set<ManagedServices.ManagedServiceInfo> removableBoundServices = new ArraySet<>();
+ removableBoundServices.add(serviceInfo);
+
+ when(service.getRemovableConnectedServices()).thenReturn(removableBoundServices);
+ ArgumentCaptor<SparseArray<Set<ComponentName>>> captor = ArgumentCaptor.forClass(
+ SparseArray.class);
+
+ service.unbindServicesImpl(switchingUserId, true);
+
+ verify(service).unbindFromServices(captor.capture());
+
+ assertEquals(captor.getValue().size(), 1);
+ assertTrue(captor.getValue().indexOfKey(userId) != -1);
+ assertTrue(captor.getValue().get(userId).contains(
+ ComponentName.unflattenFromString("a/a")));
+ }
+
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testUnbindServicesImpl_serviceOfVisibleBackgroundUser() throws Exception {
+ int switchingUserId = 10;
+ int userId = 99;
+
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ spyOn(service);
+ spyOn(service.mUmInternal);
+ when(service.mUmInternal.isVisibleBackgroundFullUser(userId)).thenReturn(true);
+
+ IInterface iInterface = mock(IInterface.class);
+ when(iInterface.asBinder()).thenReturn(mock(IBinder.class));
+
+ ManagedServices.ManagedServiceInfo serviceInfo = service.new ManagedServiceInfo(
+ iInterface, ComponentName.unflattenFromString("a/a"), userId,
+ false, mock(ServiceConnection.class), 26, 34);
+
+ Set<ManagedServices.ManagedServiceInfo> removableBoundServices = new ArraySet<>();
+ removableBoundServices.add(serviceInfo);
+
+ when(service.getRemovableConnectedServices()).thenReturn(removableBoundServices);
+ ArgumentCaptor<SparseArray<Set<ComponentName>>> captor = ArgumentCaptor.forClass(
+ SparseArray.class);
+
+ service.unbindServicesImpl(switchingUserId, true);
+
+ verify(service).unbindFromServices(captor.capture());
+
+ assertEquals(captor.getValue().size(), 0);
+ }
+
@Test
public void testOnNullBinding() throws Exception {
Context context = mock(Context.class);
@@ -1681,6 +2051,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
assertFalse(service.isBound(cn, mZero.id));
assertFalse(service.isBound(cn, mTen.id));
}
+
@Test
public void testOnPackagesChanged_nullValuesPassed_noNullPointers() {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
@@ -2012,6 +2383,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isComponentEnabledForCurrentProfiles_isThreadSafe() throws InterruptedException {
for (UserInfo userInfo : mUm.getUsers()) {
mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", userInfo.id, true);
@@ -2024,6 +2396,20 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isComponentEnabledForUser_isThreadSafe() throws InterruptedException {
+ for (UserInfo userInfo : mUm.getUsers()) {
+ mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", userInfo.id, true);
+ }
+ testThreadSafety(() -> {
+ mService.rebindServices(false, 0);
+ assertThat(mService.isComponentEnabledForUser(
+ new ComponentName("pkg1", "cmp1"), 0)).isTrue();
+ }, 20, 30);
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isComponentEnabledForCurrentProfiles_profileUserId() {
final int profileUserId = 10;
when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true);
@@ -2037,6 +2423,24 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isComponentEnabledForUser_profileUserId() {
+ final int profileUserId = 10;
+ when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true);
+ spyOn(mService);
+ doReturn(USER_CURRENT).when(mService).resolveUserId(anyInt());
+
+ // Only approve for parent user (0)
+ mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", 0, true);
+
+ // Test that the component is enabled after calling rebindServices with profile userId (10)
+ mService.rebindServices(false, profileUserId);
+ assertThat(mService.isComponentEnabledForUser(
+ new ComponentName("pkg1", "cmp1"), profileUserId)).isTrue();
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isComponentEnabledForCurrentProfiles_profileUserId_NAS() {
final int profileUserId = 10;
when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true);
@@ -2054,6 +2458,25 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isComponentEnabledForUser_profileUserId_NAS() {
+ final int profileUserId = 10;
+ when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true);
+ // Do not rebind for parent users (NAS use-case)
+ ManagedServices service = spy(mService);
+ when(service.allowRebindForParentUser()).thenReturn(false);
+ doReturn(USER_CURRENT).when(service).resolveUserId(anyInt());
+
+ // Only approve for parent user (0)
+ service.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", 0, true);
+
+ // Test that the component is disabled after calling rebindServices with profile userId (10)
+ service.rebindServices(false, profileUserId);
+ assertThat(service.isComponentEnabledForUser(
+ new ComponentName("pkg1", "cmp1"), profileUserId)).isFalse();
+ }
+
+ @Test
@EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testManagedServiceInfoIsSystemUi() {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
@@ -2069,6 +2492,48 @@ public class ManagedServicesTest extends UiServiceTestCase {
assertThat(service0.isSystemUi()).isFalse();
}
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testUserMatchesAndEnabled_profileUser() throws Exception {
+ int currentUserId = 10;
+ int profileUserId = 11;
+
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ ManagedServices.ManagedServiceInfo listener = spy(service.new ManagedServiceInfo(
+ mock(IInterface.class), ComponentName.unflattenFromString("a/a"), currentUserId,
+ false, mock(ServiceConnection.class), 26, 34));
+
+ doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(profileUserId);
+ doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(currentUserId);
+ doReturn(true).when(listener).isEnabledForUser();
+ doReturn(true).when(mUserProfiles).isCurrentProfile(anyInt());
+
+ assertThat(listener.enabledAndUserMatches(profileUserId)).isTrue();
+ }
+
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void testUserMatchesAndDisabled_visibleBackgroudUser() throws Exception {
+ int currentUserId = 10;
+ int profileUserId = 11;
+ int visibleBackgroundUserId = 12;
+
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ ManagedServices.ManagedServiceInfo listener = spy(service.new ManagedServiceInfo(
+ mock(IInterface.class), ComponentName.unflattenFromString("a/a"), profileUserId,
+ false, mock(ServiceConnection.class), 26, 34));
+
+ doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(profileUserId);
+ doReturn(currentUserId).when(service.mUmInternal).getProfileParentId(currentUserId);
+ doReturn(visibleBackgroundUserId).when(service.mUmInternal)
+ .getProfileParentId(visibleBackgroundUserId);
+ doReturn(true).when(listener).isEnabledForUser();
+
+ assertThat(listener.enabledAndUserMatches(visibleBackgroundUserId)).isFalse();
+ }
+
private void mockServiceInfoWithMetaData(List<ComponentName> componentNames,
ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas)
throws RemoteException {
@@ -2247,26 +2712,47 @@ public class ManagedServicesTest extends UiServiceTestCase {
private void verifyExpectedBoundEntries(ManagedServices service, boolean primary)
throws Exception {
+ verifyExpectedBoundEntries(service, primary, UserHandle.USER_CURRENT);
+ }
+
+ private void verifyExpectedBoundEntries(ManagedServices service, boolean primary,
+ int targetUserId) throws Exception {
ArrayMap<Integer, String> verifyMap = primary ? mExpectedPrimary.get(service.mApprovalLevel)
: mExpectedSecondary.get(service.mApprovalLevel);
for (int userId : verifyMap.keySet()) {
for (String packageOrComponent : verifyMap.get(userId).split(":")) {
if (!TextUtils.isEmpty(packageOrComponent)) {
if (service.mApprovalLevel == APPROVAL_BY_PACKAGE) {
- assertTrue(packageOrComponent,
- service.isComponentEnabledForPackage(packageOrComponent));
+ if (managedServicesConcurrentMultiuser()) {
+ assertTrue(packageOrComponent,
+ service.isComponentEnabledForPackage(packageOrComponent,
+ targetUserId));
+ } else {
+ assertTrue(packageOrComponent,
+ service.isComponentEnabledForPackage(packageOrComponent));
+ }
for (int i = 1; i <= 3; i++) {
ComponentName componentName = ComponentName.unflattenFromString(
packageOrComponent +"/C" + i);
- assertTrue(service.isComponentEnabledForCurrentProfiles(
- componentName));
+ if (managedServicesConcurrentMultiuser()) {
+ assertTrue(service.isComponentEnabledForUser(
+ componentName, targetUserId));
+ } else {
+ assertTrue(service.isComponentEnabledForCurrentProfiles(
+ componentName));
+ }
verify(mIpm, times(1)).getServiceInfo(
eq(componentName), anyLong(), anyInt());
}
} else {
ComponentName componentName =
ComponentName.unflattenFromString(packageOrComponent);
- assertTrue(service.isComponentEnabledForCurrentProfiles(componentName));
+ if (managedServicesConcurrentMultiuser()) {
+ assertTrue(service.isComponentEnabledForUser(componentName,
+ targetUserId));
+ } else {
+ assertTrue(service.isComponentEnabledForCurrentProfiles(componentName));
+ }
verify(mIpm, times(1)).getServiceInfo(
eq(componentName), anyLong(), anyInt());
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 0373eb6e9318..858dd3a605d8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -140,6 +140,7 @@ import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
+import static com.android.server.notification.Flags.FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER;
import static com.android.server.notification.Flags.FLAG_REJECT_OLD_NOTIFICATIONS;
import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION;
@@ -867,7 +868,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
&& filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) {
mPackageIntentReceiver = broadcastReceivers.get(i);
}
- if (filter.hasAction(Intent.ACTION_USER_SWITCHED)
+ if (filter.hasAction(Intent.ACTION_USER_STOPPED)
+ || filter.hasAction(Intent.ACTION_USER_SWITCHED)
|| filter.hasAction(Intent.ACTION_PROFILE_UNAVAILABLE)
|| filter.hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
// There may be multiple receivers, get the NMS one
@@ -5383,6 +5385,42 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testGetPackagesWithChannels_blocked() throws Exception {
+ // While we mostly rely on the PreferencesHelper implementation of channels, we filter in
+ // NMS so that we do not return blocked packages.
+ // Three packages; all under user 1.
+ // pkg2 is blocked, but pkg1 and pkg3 are not.
+ String pkg1 = "com.package.one", pkg2 = "com.package.two", pkg3 = "com.package.three";
+ int uid1 = UserHandle.getUid(1, 111);
+ int uid2 = UserHandle.getUid(1, 222);
+ int uid3 = UserHandle.getUid(1, 333);
+
+ when(mPackageManager.getPackageUid(eq(pkg1), anyLong(), anyInt())).thenReturn(uid1);
+ when(mPackageManager.getPackageUid(eq(pkg2), anyLong(), anyInt())).thenReturn(uid2);
+ when(mPackageManager.getPackageUid(eq(pkg3), anyLong(), anyInt())).thenReturn(uid3);
+ when(mPermissionHelper.hasPermission(uid1)).thenReturn(true);
+ when(mPermissionHelper.hasPermission(uid2)).thenReturn(false);
+ when(mPermissionHelper.hasPermission(uid3)).thenReturn(true);
+
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ NotificationChannel channel2 = new NotificationChannel("id3", "name3",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ NotificationChannel channel3 = new NotificationChannel("id4", "name3",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ mService.mPreferencesHelper.createNotificationChannel(pkg1, uid1, channel1, true, false,
+ uid1, false);
+ mService.mPreferencesHelper.createNotificationChannel(pkg2, uid2, channel2, true, false,
+ uid2, false);
+ mService.mPreferencesHelper.createNotificationChannel(pkg3, uid3, channel3, true, false,
+ uid3, false);
+
+ // Output should contain only the package with notification permissions (1, 3).
+ enableInteractAcrossUsers();
+ assertThat(mBinderService.getPackagesWithAnyChannels(1)).containsExactly(pkg1, pkg3);
+ }
+
+ @Test
public void testHasCompanionDevice_failure() throws Exception {
when(mCompanionMgr.getAssociations(anyString(), anyInt())).thenThrow(
new IllegalArgumentException());
@@ -16287,6 +16325,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void onUserStopped_callBackToListeners() {
+ Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, 20);
+
+ mUserIntentReceiver.onReceive(mContext, intent);
+
+ verify(mConditionProviders).onUserStopped(eq(20));
+ verify(mListeners).onUserStopped(eq(20));
+ verify(mAssistants).onUserStopped(eq(20));
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isNotificationPolicyAccessGranted_invalidPackage() throws Exception {
final String notReal = "NOT REAL";
final var checker = mService.permissionChecker;
@@ -16303,6 +16355,25 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isNotificationPolicyAccessGranted_invalidPackage_concurrent_multiUser()
+ throws Exception {
+ final String notReal = "NOT REAL";
+ final var checker = mService.permissionChecker;
+
+ when(mPackageManagerClient.getPackageUidAsUser(eq(notReal), anyInt())).thenThrow(
+ PackageManager.NameNotFoundException.class);
+
+ assertThat(mBinderService.isNotificationPolicyAccessGranted(notReal)).isFalse();
+ verify(mPackageManagerClient).getPackageUidAsUser(eq(notReal), anyInt());
+ verify(checker, never()).check(any(), anyInt(), anyInt(), anyBoolean());
+ verify(mConditionProviders, never()).isPackageOrComponentAllowed(eq(notReal), anyInt());
+ verify(mListeners, never()).isComponentEnabledForPackage(any(), anyInt());
+ verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isNotificationPolicyAccessGranted_hasPermission() throws Exception {
final String packageName = "target";
final int uid = 123;
@@ -16321,6 +16392,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isNotificationPolicyAccessGranted_hasPermission_concurrent_multiUser()
+ throws Exception {
+ final String packageName = "target";
+ final int uid = 123;
+ final var checker = mService.permissionChecker;
+
+ when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+ when(checker.check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue();
+ verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+ verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+ verify(mConditionProviders, never()).isPackageOrComponentAllowed(eq(packageName), anyInt());
+ verify(mListeners, never()).isComponentEnabledForPackage(any(), anyInt());
+ verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isNotificationPolicyAccessGranted_isPackageAllowed() throws Exception {
final String packageName = "target";
final int uid = 123;
@@ -16339,6 +16431,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isNotificationPolicyAccessGranted_isPackageAllowed_concurrent_multiUser()
+ throws Exception {
+ final String packageName = "target";
+ final int uid = 123;
+ final var checker = mService.permissionChecker;
+
+ when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+ when(mConditionProviders.isPackageOrComponentAllowed(eq(packageName), anyInt()))
+ .thenReturn(true);
+
+ assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue();
+ verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+ verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+ verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+ verify(mListeners, never()).isComponentEnabledForPackage(any(), anyInt());
+ verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isNotificationPolicyAccessGranted_isComponentEnabled() throws Exception {
final String packageName = "target";
final int uid = 123;
@@ -16356,6 +16469,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isNotificationPolicyAccessGranted_isComponentEnabled_concurrent_multiUser()
+ throws Exception {
+ final String packageName = "target";
+ final int uid = 123;
+ final var checker = mService.permissionChecker;
+
+ when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+ when(mListeners.isComponentEnabledForPackage(packageName, mUserId)).thenReturn(true);
+
+ assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue();
+ verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+ verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+ verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+ verify(mListeners).isComponentEnabledForPackage(packageName, mUserId);
+ verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isNotificationPolicyAccessGranted_isDeviceOwner() throws Exception {
final String packageName = "target";
final int uid = 123;
@@ -16372,10 +16505,30 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(mDevicePolicyManager).isActiveDeviceOwner(uid);
}
+ @Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isNotificationPolicyAccessGranted_isDeviceOwner_concurrent_multiUser()
+ throws Exception {
+ final String packageName = "target";
+ final int uid = 123;
+ final var checker = mService.permissionChecker;
+
+ when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+ when(mDevicePolicyManager.isActiveDeviceOwner(uid)).thenReturn(true);
+
+ assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue();
+ verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+ verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+ verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+ verify(mListeners).isComponentEnabledForPackage(packageName, mUserId);
+ verify(mDevicePolicyManager).isActiveDeviceOwner(uid);
+ }
+
/**
* b/292163859
*/
@Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isNotificationPolicyAccessGranted_callerIsDeviceOwner() throws Exception {
final String packageName = "target";
final int uid = 123;
@@ -16394,7 +16547,32 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(mDevicePolicyManager, never()).isActiveDeviceOwner(callingUid);
}
+ /**
+ * b/292163859
+ */
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isNotificationPolicyAccessGranted_callerIsDeviceOwner_concurrent_multiUser()
+ throws Exception {
+ final String packageName = "target";
+ final int uid = 123;
+ final int callingUid = Binder.getCallingUid();
+ final var checker = mService.permissionChecker;
+
+ when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+ when(mDevicePolicyManager.isActiveDeviceOwner(callingUid)).thenReturn(true);
+
+ assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isFalse();
+ verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+ verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+ verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+ verify(mListeners).isComponentEnabledForPackage(packageName, mUserId);
+ verify(mDevicePolicyManager).isActiveDeviceOwner(uid);
+ verify(mDevicePolicyManager, never()).isActiveDeviceOwner(callingUid);
+ }
+
+ @Test
+ @DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void isNotificationPolicyAccessGranted_notGranted() throws Exception {
final String packageName = "target";
final int uid = 123;
@@ -16411,6 +16589,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
+ public void isNotificationPolicyAccessGranted_notGranted_concurrent_multiUser()
+ throws Exception {
+ final String packageName = "target";
+ final int uid = 123;
+ final var checker = mService.permissionChecker;
+
+ when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+
+ assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isFalse();
+ verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+ verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+ verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+ verify(mListeners).isComponentEnabledForPackage(packageName, mUserId);
+ verify(mDevicePolicyManager).isActiveDeviceOwner(uid);
+ }
+
+ @Test
public void testResetDefaultDnd() {
TestableNotificationManagerService service = spy(mService);
UserInfo user = new UserInfo(0, "owner", 0);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 3f26cd9258af..640de174ba20 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -3096,6 +3096,67 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
+ public void getPackagesWithAnyChannels_noChannels() {
+ assertThat(mHelper.getPackagesWithAnyChannels(UserHandle.getUserId(UID_O))).isEmpty();
+ }
+
+ @Test
+ public void getPackagesWithAnyChannels_someChannels() {
+ // 2 channels under PKG_N_MR1, 1 under PKG_O
+ NotificationChannel channel1 = new NotificationChannel("1", "something",
+ IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false, UID_N_MR1,
+ false);
+ NotificationChannel channel2 = new NotificationChannel("2", "another", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, true, false, UID_N_MR1,
+ false);
+
+ NotificationChannel other = new NotificationChannel("3", "still another",
+ IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_O, UID_O, other, true, false, UID_O, false);
+
+ assertThat(mHelper.getPackagesWithAnyChannels(USER.getIdentifier())).containsExactly(
+ PKG_N_MR1, PKG_O);
+ }
+
+ @Test
+ public void getPackagesWithAnyChannels_onlyDeleted() {
+ NotificationChannel channel1 = new NotificationChannel("1", "something",
+ IMPORTANCE_DEFAULT);
+ channel1.setDeleted(true);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel1, true, false, UID_O,
+ false);
+ NotificationChannel channel2 = new NotificationChannel("2", "another", IMPORTANCE_DEFAULT);
+ channel2.setDeleted(true);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false, UID_O,
+ false);
+
+ assertThat(mHelper.getPackagesWithAnyChannels(UserHandle.getUserId(UID_O))).isEmpty();
+ }
+
+ @Test
+ public void getPackagesWithAnyChannels_distinguishesUsers() throws Exception {
+ // Set a package up for both users 0 and 10
+ String pkgName = "test.package";
+ int uid0 = UserHandle.getUid(0, 1234);
+ int uid10 = UserHandle.getUid(10, 1234);
+ setUpPackageWithUid(pkgName, uid0);
+ setUpPackageWithUid(pkgName, uid10);
+
+ // but only user 10 has channels
+ NotificationChannel channel1 = new NotificationChannel("1", "something",
+ IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(pkgName, uid10, channel1, true, false, uid10,
+ false);
+ NotificationChannel channel2 = new NotificationChannel("2", "another", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(pkgName, uid10, channel2, true, false, uid10,
+ false);
+
+ assertThat(mHelper.getPackagesWithAnyChannels(0)).isEmpty();
+ assertThat(mHelper.getPackagesWithAnyChannels(10)).containsExactly(pkgName);
+ }
+
+ @Test
public void testOnlyHasDefaultChannel() throws Exception {
assertTrue(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1));
assertFalse(mHelper.onlyHasDefaultChannel(PKG_O, UID_O));
diff --git a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
index 993c3fed9d59..2eb8ba1be811 100644
--- a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
+++ b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
@@ -16,6 +16,7 @@
package android.animation
+import android.content.pm.PackageManager
import android.graphics.Color
import android.platform.test.annotations.MotionTest
import android.view.ViewGroup
@@ -23,11 +24,14 @@ import android.widget.FrameLayout
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.internal.dynamicanimation.animation.DynamicAnimation
import com.android.internal.dynamicanimation.animation.SpringAnimation
import com.android.internal.dynamicanimation.animation.SpringForce
import kotlinx.coroutines.test.TestScope
+import org.junit.Assume.assumeFalse
+import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -65,6 +69,16 @@ class AnimatorTestRuleToolkitTest {
bitmapDiffer = screenshotRule,
)
+ @Before
+ fun setUp() {
+ // Do not run on Automotive.
+ assumeFalse(
+ InstrumentationRegistry.getInstrumentation().context.packageManager.hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE
+ )
+ )
+ }
+
@Test
fun recordFilmstrip_withAnimator() {
val animatedBox = createScene()
@@ -188,12 +202,7 @@ class AnimatorTestRuleToolkitTest {
null
}
return motionRule.recordMotion(
- AnimatorRuleRecordingSpec(
- container,
- motionControl,
- sampleIntervalMs,
- visualCapture,
- ) {
+ AnimatorRuleRecordingSpec(container, motionControl, sampleIntervalMs, visualCapture) {
feature(ViewFeatureCaptures.alpha, "alpha")
}
)