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 8a4b6e31c78ab..fee1f5c2a5597 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -883,6 +883,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);