Don't start dreams immediately if keyguard is occluded when docked.
The keyguard must be showing and unoccluded, or the device is not
interactive (eg screen off) in order for dreams to be started
immediately when docked. This prevents dreams from starting if there is
an activity currently showing over the lockscreen.
Also includes some refactoring to be able to unit test this change,
since dock state changes were not previously tested.
Fixes: 233412496
Test: atest UiModeManagerServiceTest
Test: manually on device with keyguard occluded, unoccluded, and screen
off while docked
Change-Id: I12f1a4cf3ae4784e391e3c5a3fd1d50b4d4f7ecd
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index 7bf5c38..6956cd4 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -46,6 +46,12 @@
public abstract boolean isDreaming();
/**
+ * Ask the power manager to nap. It will eventually call back into startDream() if/when it is
+ * appropriate to start dreaming.
+ */
+ public abstract void requestDream();
+
+ /**
* Called by the ActivityTaskManagerService to verify that the startDreamActivity
* request comes from the current active dream component.
*
diff --git a/core/java/android/service/dreams/Sandman.java b/core/java/android/service/dreams/Sandman.java
index ced2a01..fae72a2 100644
--- a/core/java/android/service/dreams/Sandman.java
+++ b/core/java/android/service/dreams/Sandman.java
@@ -20,13 +20,13 @@
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
+import com.android.server.LocalServices;
+
/**
* Internal helper for launching dreams to ensure consistency between the
* <code>UiModeManagerService</code> system service and the <code>Somnambulator</code> activity.
@@ -75,32 +75,28 @@
}
private static void startDream(Context context, boolean docked) {
- try {
- IDreamManager dreamManagerService = IDreamManager.Stub.asInterface(
- ServiceManager.getService(DreamService.DREAM_SERVICE));
- if (dreamManagerService != null && !dreamManagerService.isDreaming()) {
- if (docked) {
- Slog.i(TAG, "Activating dream while docked.");
+ DreamManagerInternal dreamManagerService =
+ LocalServices.getService(DreamManagerInternal.class);
+ if (dreamManagerService != null && !dreamManagerService.isDreaming()) {
+ if (docked) {
+ Slog.i(TAG, "Activating dream while docked.");
- // Wake up.
- // The power manager will wake up the system automatically when it starts
- // receiving power from a dock but there is a race between that happening
- // and the UI mode manager starting a dream. We want the system to already
- // be awake by the time this happens. Otherwise the dream may not start.
- PowerManager powerManager =
- context.getSystemService(PowerManager.class);
- powerManager.wakeUp(SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_PLUGGED_IN,
- "android.service.dreams:DREAM");
- } else {
- Slog.i(TAG, "Activating dream by user request.");
- }
-
- // Dream.
- dreamManagerService.dream();
+ // Wake up.
+ // The power manager will wake up the system automatically when it starts
+ // receiving power from a dock but there is a race between that happening
+ // and the UI mode manager starting a dream. We want the system to already
+ // be awake by the time this happens. Otherwise the dream may not start.
+ PowerManager powerManager =
+ context.getSystemService(PowerManager.class);
+ powerManager.wakeUp(SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_PLUGGED_IN,
+ "android.service.dreams:DREAM");
+ } else {
+ Slog.i(TAG, "Activating dream by user request.");
}
- } catch (RemoteException ex) {
- Slog.e(TAG, "Could not start dream when docked.", ex);
+
+ // Dream.
+ dreamManagerService.requestDream();
}
}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index c853ba9..e9dd836 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -359,6 +359,11 @@
SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode));
}
+ @VisibleForTesting
+ void setStartDreamImmediatelyOnDock(boolean startDreamImmediatelyOnDock) {
+ mStartDreamImmediatelyOnDock = startDreamImmediatelyOnDock;
+ }
+
@Override
public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
mCurrentUser = to.getUserIdentifier();
@@ -1824,7 +1829,8 @@
// If we did not start a dock app, then start dreaming if appropriate.
if (category != null && !dockAppStarted && (mStartDreamImmediatelyOnDock
- || mKeyguardManager.isKeyguardLocked())) {
+ || mWindowManager.isKeyguardShowingAndNotOccluded()
+ || !mPowerManager.isInteractive())) {
Sandman.startDreamWhenDockedIfAppropriate(getContext());
}
}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 4e0489a..fb01ce5 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -842,6 +842,11 @@
public ComponentName getActiveDreamComponent(boolean doze) {
return getActiveDreamComponentInternal(doze);
}
+
+ @Override
+ public void requestDream() {
+ requestDreamInternal();
+ }
}
private final Runnable mSystemPropertiesChanged = new Runnable() {
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 2128a08..3d6736e 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -20,6 +20,7 @@
],
static_libs: [
+ "frameworks-base-testutils",
"services.accessibility",
"services.core",
"services.devicepolicy",
@@ -32,6 +33,7 @@
"platformprotosnano",
"statsdprotolite",
"hamcrest-library",
+ "servicestests-utils",
"testables",
"truth-prebuilt",
// TODO: remove once Android migrates to JUnit 4.12,
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index aee2755..617a34f 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -45,6 +45,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
@@ -58,31 +59,36 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
import static org.testng.Assert.assertThrows;
import android.Manifest;
+import android.app.Activity;
import android.app.AlarmManager;
import android.app.IOnProjectionStateChangedListener;
import android.app.IUiModeManager;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Process;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.dreams.DreamManagerInternal;
+import android.test.mock.MockContentResolver;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
@@ -93,6 +99,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import java.time.LocalDateTime;
@@ -107,8 +114,7 @@
private static final String PACKAGE_NAME = "Diane Coffee";
private UiModeManagerService mUiManagerService;
private IUiModeManager mService;
- @Mock
- private ContentResolver mContentResolver;
+ private MockContentResolver mContentResolver;
@Mock
private WindowManagerInternal mWindowManager;
@Mock
@@ -131,16 +137,22 @@
private PackageManager mPackageManager;
@Mock
private IBinder mBinder;
+ @Mock
+ private DreamManagerInternal mDreamManager;
+ @Captor
+ private ArgumentCaptor<Intent> mOrderedBroadcastIntent;
+ @Captor
+ private ArgumentCaptor<BroadcastReceiver> mOrderedBroadcastReceiver;
private BroadcastReceiver mScreenOffCallback;
private BroadcastReceiver mTimeChangedCallback;
+ private BroadcastReceiver mDockStateChangedCallback;
private AlarmManager.OnAlarmListener mCustomListener;
private Consumer<PowerSaveState> mPowerSaveConsumer;
private TwilightListener mTwilightListener;
@Before
public void setUp() {
- initMocks(this);
when(mContext.checkCallingOrSelfPermission(anyString()))
.thenReturn(PackageManager.PERMISSION_GRANTED);
doAnswer(inv -> {
@@ -154,6 +166,10 @@
when(mLocalPowerManager.getLowPowerState(anyInt()))
.thenReturn(new PowerSaveState.Builder().setBatterySaverEnabled(false).build());
when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getString(com.android.internal.R.string.config_somnambulatorComponent))
+ .thenReturn("somnambulator");
+ mContentResolver = new MockContentResolver();
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPowerManager.isInteractive()).thenReturn(true);
@@ -168,6 +184,9 @@
if (filter.hasAction(Intent.ACTION_SCREEN_OFF)) {
mScreenOffCallback = inv.getArgument(0);
}
+ if (filter.hasAction(Intent.ACTION_DOCK_EVENT)) {
+ mDockStateChangedCallback = inv.getArgument(0);
+ }
return null;
});
doAnswer(inv -> {
@@ -182,11 +201,13 @@
}).when(mAlarmManager).cancel(eq(mCustomListener));
when(mContext.getSystemService(eq(Context.POWER_SERVICE)))
.thenReturn(mPowerManager);
+ when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
when(mContext.getSystemService(eq(Context.ALARM_SERVICE)))
.thenReturn(mAlarmManager);
addLocalService(WindowManagerInternal.class, mWindowManager);
addLocalService(PowerManagerInternal.class, mLocalPowerManager);
addLocalService(TwilightManager.class, mTwilightManager);
+ addLocalService(DreamManagerInternal.class, mDreamManager);
mUiManagerService = new UiModeManagerService(mContext, /* setupWizardComplete= */ true,
mTwilightManager, new TestInjector());
@@ -1298,6 +1319,138 @@
assertThat(mService.getCurrentModeType()).isNotEqualTo(Configuration.UI_MODE_TYPE_CAR);
}
+ @Test
+ public void dreamWhenDocked() {
+ setScreensaverActivateOnDock(true);
+ setScreensaverEnabled(true);
+
+ triggerDockIntent();
+ verifyAndSendResultBroadcast();
+ verify(mDreamManager).requestDream();
+ }
+
+ @Test
+ public void noDreamWhenDocked_dreamsDisabled() {
+ setScreensaverActivateOnDock(true);
+ setScreensaverEnabled(false);
+
+ triggerDockIntent();
+ verifyAndSendResultBroadcast();
+ verify(mDreamManager, never()).requestDream();
+ }
+
+ @Test
+ public void noDreamWhenDocked_dreamsWhenDockedDisabled() {
+ setScreensaverActivateOnDock(false);
+ setScreensaverEnabled(true);
+
+ triggerDockIntent();
+ verifyAndSendResultBroadcast();
+ verify(mDreamManager, never()).requestDream();
+ }
+
+ @Test
+ public void noDreamWhenDocked_keyguardNotShowing_interactive() {
+ setScreensaverActivateOnDock(true);
+ setScreensaverEnabled(true);
+ mUiManagerService.setStartDreamImmediatelyOnDock(false);
+ when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+
+ triggerDockIntent();
+ verifyAndSendResultBroadcast();
+ verify(mDreamManager, never()).requestDream();
+ }
+
+ @Test
+ public void dreamWhenDocked_keyguardShowing_interactive() {
+ setScreensaverActivateOnDock(true);
+ setScreensaverEnabled(true);
+ mUiManagerService.setStartDreamImmediatelyOnDock(false);
+ when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(true);
+ when(mPowerManager.isInteractive()).thenReturn(false);
+
+ triggerDockIntent();
+ verifyAndSendResultBroadcast();
+ verify(mDreamManager).requestDream();
+ }
+
+ @Test
+ public void dreamWhenDocked_keyguardNotShowing_notInteractive() {
+ setScreensaverActivateOnDock(true);
+ setScreensaverEnabled(true);
+ mUiManagerService.setStartDreamImmediatelyOnDock(false);
+ when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(false);
+ when(mPowerManager.isInteractive()).thenReturn(false);
+
+ triggerDockIntent();
+ verifyAndSendResultBroadcast();
+ verify(mDreamManager).requestDream();
+ }
+
+ @Test
+ public void dreamWhenDocked_keyguardShowing_notInteractive() {
+ setScreensaverActivateOnDock(true);
+ setScreensaverEnabled(true);
+ mUiManagerService.setStartDreamImmediatelyOnDock(false);
+ when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(true);
+ when(mPowerManager.isInteractive()).thenReturn(false);
+
+ triggerDockIntent();
+ verifyAndSendResultBroadcast();
+ verify(mDreamManager).requestDream();
+ }
+
+ private void triggerDockIntent() {
+ final Intent dockedIntent =
+ new Intent(Intent.ACTION_DOCK_EVENT)
+ .putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_DESK);
+ mDockStateChangedCallback.onReceive(mContext, dockedIntent);
+ }
+
+ private void verifyAndSendResultBroadcast() {
+ verify(mContext).sendOrderedBroadcastAsUser(
+ mOrderedBroadcastIntent.capture(),
+ any(UserHandle.class),
+ nullable(String.class),
+ mOrderedBroadcastReceiver.capture(),
+ nullable(Handler.class),
+ anyInt(),
+ nullable(String.class),
+ nullable(Bundle.class));
+
+ mOrderedBroadcastReceiver.getValue().setPendingResult(
+ new BroadcastReceiver.PendingResult(
+ Activity.RESULT_OK,
+ /* resultData= */ "",
+ /* resultExtras= */ null,
+ /* type= */ 0,
+ /* ordered= */ true,
+ /* sticky= */ false,
+ /* token= */ null,
+ /* userId= */ 0,
+ /* flags= */ 0));
+ mOrderedBroadcastReceiver.getValue().onReceive(
+ mContext,
+ mOrderedBroadcastIntent.getValue());
+ }
+
+ private void setScreensaverEnabled(boolean enable) {
+ Settings.Secure.putIntForUser(
+ mContentResolver,
+ Settings.Secure.SCREENSAVER_ENABLED,
+ enable ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ private void setScreensaverActivateOnDock(boolean enable) {
+ Settings.Secure.putIntForUser(
+ mContentResolver,
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ enable ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
private void requestAllPossibleProjectionTypes() throws RemoteException {
for (int i = 0; i < Integer.SIZE; ++i) {
mService.requestProjection(mBinder, 1 << i, PACKAGE_NAME);