diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index 7bf5c38b7fd49..6956cd4cae6b8 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -45,6 +45,12 @@ public abstract class DreamManagerInternal {
*/
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 ced2a01cb1d0d..fae72a2e91a89 100644
--- a/core/java/android/service/dreams/Sandman.java
+++ b/core/java/android/service/dreams/Sandman.java
@@ -20,13 +20,13 @@ import android.content.ComponentName;
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
* UiModeManagerService system service and the Somnambulator activity.
@@ -75,32 +75,28 @@ public final class Sandman {
}
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 c853ba93f4ab3..e9dd836e527af 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -359,6 +359,11 @@ final class UiModeManagerService extends SystemService {
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 @@ final class UiModeManagerService extends SystemService {
// 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 4e0489a5c4bc3..fb01ce5c94404 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 final class DreamManagerService extends SystemService {
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 2128a08d53d20..3d6736e33cacb 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -20,6 +20,7 @@ android_test {
],
static_libs: [
+ "frameworks-base-testutils",
"services.accessibility",
"services.core",
"services.devicepolicy",
@@ -32,6 +33,7 @@ android_test {
"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 aee27554b023b..617a34f44bff4 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.anyLong;
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.verify;
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.Ignore;
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 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
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 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
private PackageManager mPackageManager;
@Mock
private IBinder mBinder;
+ @Mock
+ private DreamManagerInternal mDreamManager;
+ @Captor
+ private ArgumentCaptor mOrderedBroadcastIntent;
+ @Captor
+ private ArgumentCaptor mOrderedBroadcastReceiver;
private BroadcastReceiver mScreenOffCallback;
private BroadcastReceiver mTimeChangedCallback;
+ private BroadcastReceiver mDockStateChangedCallback;
private AlarmManager.OnAlarmListener mCustomListener;
private Consumer mPowerSaveConsumer;
private TwilightListener mTwilightListener;
@Before
public void setUp() {
- initMocks(this);
when(mContext.checkCallingOrSelfPermission(anyString()))
.thenReturn(PackageManager.PERMISSION_GRANTED);
doAnswer(inv -> {
@@ -154,6 +166,10 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
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 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
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 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
}).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 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
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);