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
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
* <code>UiModeManagerService</code> system service and the <code>Somnambulator</code> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<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 @@ 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);
|
||||
|
||||
Reference in New Issue
Block a user