Fix problem with AOD when secondary power groups are awake

Bug: 279845136
Test: atest UnlockedScreenOffAnimationControllerTest PowerManagerServiceTest
Change-Id: I129ef065f57c494333cbf8b82d3355344fdc8be1
This commit is contained in:
Antony Sargent
2023-03-31 04:29:01 -07:00
parent abbe7c5638
commit 956f4084df
6 changed files with 217 additions and 9 deletions

View File

@@ -55,6 +55,7 @@ interface IPowerManager
float getBrightnessConstraint(int constraint);
@UnsupportedAppUsage
boolean isInteractive();
boolean isDisplayInteractive(int displayId);
boolean areAutoPowerSaveModesEnabled();
boolean isPowerSaveMode();
PowerSaveState getPowerSaveState(int serviceType);

View File

@@ -1150,13 +1150,17 @@ public final class PowerManager {
}
};
private final PropertyInvalidatedCache<Void, Boolean> mInteractiveCache =
new PropertyInvalidatedCache<Void, Boolean>(MAX_CACHE_ENTRIES,
private final PropertyInvalidatedCache<Integer, Boolean> mInteractiveCache =
new PropertyInvalidatedCache<Integer, Boolean>(MAX_CACHE_ENTRIES,
CACHE_KEY_IS_INTERACTIVE_PROPERTY) {
@Override
public Boolean recompute(Void query) {
public Boolean recompute(Integer displayId) {
try {
return mService.isInteractive();
if (displayId == null) {
return mService.isInteractive();
} else {
return mService.isDisplayInteractive(displayId);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1802,6 +1806,18 @@ public final class PowerManager {
return mInteractiveCache.query(null);
}
/**
* Returns the interactive state for a specific display, which may not be the same as the
* global wakefulness (which is true when any display is awake).
*
* @param displayId
* @return whether the given display is present and interactive, or false
*
* @hide
*/
public boolean isInteractive(int displayId) {
return mInteractiveCache.query(displayId);
}
/**
* Returns {@code true} if this device supports rebooting userspace.

View File

@@ -8,6 +8,7 @@ import android.database.ContentObserver
import android.os.Handler
import android.os.PowerManager
import android.provider.Settings
import android.view.Display
import android.view.Surface
import android.view.View
import android.view.WindowManager.fixScale
@@ -272,7 +273,7 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// dispatched, a race condition could make it possible for this callback to be run
// as the device is waking up. That results in the AOD UI being shown while we wake
// up, with unpredictable consequences.
if (!powerManager.isInteractive) {
if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY)) {
aodUiAnimationPlaying = true
// Show AOD. That'll cause the KeyguardVisibilityHelper to call

View File

@@ -20,6 +20,7 @@ import android.os.Handler
import android.os.PowerManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.Display
import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.SysuiTestCase
@@ -29,6 +30,7 @@ import com.android.systemui.shade.ShadeViewController
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.StatusBarStateControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.settings.GlobalSettings
import junit.framework.Assert.assertFalse
import org.junit.After
@@ -141,7 +143,7 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
@Test
fun testAodUiNotShownIfInteractive() {
`when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
`when`(powerManager.isInteractive).thenReturn(true)
`when`(powerManager.isInteractive(eq(Display.DEFAULT_DISPLAY))).thenReturn(true)
val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
controller.startAnimation()
@@ -152,6 +154,21 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
verify(shadeViewController, never()).showAodUi()
}
@Test
fun testAodUiShownIfGloballyInteractiveButDefaultDisplayNotInteractive() {
`when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
`when`(powerManager.isInteractive()).thenReturn(false)
`when`(powerManager.isInteractive(eq(Display.DEFAULT_DISPLAY))).thenReturn(false)
val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
controller.startAnimation()
verify(handler).postDelayed(callbackCaptor.capture(), anyLong())
callbackCaptor.value.run()
verify(shadeViewController).showAodUi()
}
@Test
fun testNoAnimationPlaying_dozeParamsCanNotControlScreenOff() {
`when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(false)

View File

@@ -2079,6 +2079,7 @@ public final class PowerManagerService extends SystemService
int opUid, String opPackageName, String details) {
mPowerGroups.get(groupId).setWakefulnessLocked(wakefulness, eventTime, uid, reason, opUid,
opPackageName, details);
mInjector.invalidateIsInteractiveCaches();
}
@SuppressWarnings("deprecation")
@@ -3743,12 +3744,32 @@ public final class PowerManagerService extends SystemService
}
}
private boolean isInteractiveInternal() {
private boolean isGloballyInteractiveInternal() {
synchronized (mLock) {
return PowerManagerInternal.isInteractive(getGlobalWakefulnessLocked());
}
}
private boolean isInteractiveInternal(int displayId, int uid) {
synchronized (mLock) {
DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(displayId);
if (displayInfo == null) {
Slog.w(TAG, "Did not find DisplayInfo for displayId " + displayId);
return false;
}
if (!displayInfo.hasAccess(uid)) {
throw new SecurityException(
"uid " + uid + " does not have access to display " + displayId);
}
PowerGroup powerGroup = mPowerGroups.get(displayInfo.displayGroupId);
if (powerGroup == null) {
Slog.w(TAG, "Did not find PowerGroup for displayId " + displayId);
return false;
}
return PowerManagerInternal.isInteractive(powerGroup.getWakefulnessLocked());
}
}
private boolean setLowPowerModeInternal(boolean enabled) {
synchronized (mLock) {
if (DEBUG) {
@@ -5805,7 +5826,18 @@ public final class PowerManagerService extends SystemService
public boolean isInteractive() {
final long ident = Binder.clearCallingIdentity();
try {
return isInteractiveInternal();
return isGloballyInteractiveInternal();
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean isDisplayInteractive(int displayId) {
int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
return isInteractiveInternal(displayId, uid);
} finally {
Binder.restoreCallingIdentity(ident);
}

View File

@@ -74,6 +74,7 @@ import android.os.IBinder;
import android.os.IWakeLockCallback;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.UserHandle;
import android.os.test.TestLooper;
@@ -117,6 +118,7 @@ import org.mockito.stubbing.Answer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
@@ -150,6 +152,7 @@ public class PowerManagerServiceTest {
@Mock private SystemPropertiesWrapper mSystemPropertiesMock;
@Mock private AppOpsManager mAppOpsManagerMock;
@Mock private LowPowerStandbyController mLowPowerStandbyControllerMock;
@Mock private Callable<Void> mInvalidateInteractiveCachesMock;
@Mock
private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
@@ -310,7 +313,11 @@ public class PowerManagerServiceTest {
@Override
void invalidateIsInteractiveCaches() {
// Avoids an SELinux failure.
try {
mInvalidateInteractiveCachesMock.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
@@ -2317,6 +2324,140 @@ public class PowerManagerServiceTest {
eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), eq(eventTime));
}
@Test
public void testMultiDisplay_isInteractive_nonExistentGroup() {
createService();
startSystem();
int nonExistentDisplayGroup = 999;
BinderService binderService = mService.getBinderServiceInstance();
assertThat(binderService.isDisplayInteractive(nonExistentDisplayGroup)).isFalse();
}
private void testMultiDisplay_isInteractive_returnsCorrectValue(
boolean defaultDisplayAwake, boolean secondGroupDisplayAwake) {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
// We use a display id that does not match the group id, to make sure we aren't accidentally
// confusing display id's and display group id's in the implementation.
final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 17;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
new AtomicReference<>();
doAnswer((Answer<Void>) invocation -> {
listener.set(invocation.getArgument(0));
return null;
}).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
final DisplayInfo defaultDisplayInfo = new DisplayInfo();
defaultDisplayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(
defaultDisplayInfo);
final DisplayInfo secondDisplayInfo = new DisplayInfo();
secondDisplayInfo.displayGroupId = nonDefaultDisplayGroupId;
when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(
secondDisplayInfo);
createService();
startSystem();
listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
if (!defaultDisplayAwake) {
mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP,
mClock.now(), 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
}
if (!secondGroupDisplayAwake) {
mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP,
mClock.now(), 0,
PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
}
assertThat(PowerManagerInternal.isInteractive(
mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))).isEqualTo(
defaultDisplayAwake);
assertThat(PowerManagerInternal.isInteractive(
mService.getWakefulnessLocked(nonDefaultDisplayGroupId))).isEqualTo(
secondGroupDisplayAwake);
BinderService binderService = mService.getBinderServiceInstance();
assertThat(binderService.isInteractive()).isEqualTo(
defaultDisplayAwake || secondGroupDisplayAwake);
assertThat(binderService.isDisplayInteractive(Display.DEFAULT_DISPLAY)).isEqualTo(
defaultDisplayAwake);
assertThat(binderService.isDisplayInteractive(nonDefaultDisplay)).isEqualTo(
secondGroupDisplayAwake);
}
@Test
public void testMultiDisplay_isInteractive_defaultGroupIsAwakeSecondGroupIsAwake() {
testMultiDisplay_isInteractive_returnsCorrectValue(true, true);
}
@Test
public void testMultiDisplay_isInteractive_defaultGroupIsAwakeSecondGroupIsAsleep() {
testMultiDisplay_isInteractive_returnsCorrectValue(true, false);
}
@Test
public void testMultiDisplay_isInteractive_defaultGroupIsAsleepSecondGroupIsAwake() {
testMultiDisplay_isInteractive_returnsCorrectValue(false, true);
}
@Test
public void testMultiDisplay_isInteractive_bothGroupsAreAsleep() {
testMultiDisplay_isInteractive_returnsCorrectValue(false, false);
}
@Test
public void testMultiDisplay_defaultGroupWakefulnessChange_causesIsInteractiveInvalidate()
throws Exception {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
new AtomicReference<>();
doAnswer((Answer<Void>) invocation -> {
listener.set(invocation.getArgument(0));
return null;
}).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
final DisplayInfo info = new DisplayInfo();
info.displayGroupId = nonDefaultDisplayGroupId;
when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
createService();
startSystem();
listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
verify(mInvalidateInteractiveCachesMock).call();
mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP,
mClock.now(), 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
verify(mInvalidateInteractiveCachesMock, times(2)).call();
}
@Test
public void testMultiDisplay_secondGroupWakefulness_causesIsInteractiveInvalidate()
throws Exception {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
new AtomicReference<>();
doAnswer((Answer<Void>) invocation -> {
listener.set(invocation.getArgument(0));
return null;
}).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
final DisplayInfo info = new DisplayInfo();
info.displayGroupId = nonDefaultDisplayGroupId;
when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
createService();
startSystem();
listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
verify(mInvalidateInteractiveCachesMock).call();
mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, mClock.now(),
0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
verify(mInvalidateInteractiveCachesMock, times(2)).call();
}
@Test
public void testGetFullPowerSavePolicy_returnsStateMachineResult() {
createService();