diff options
6 files changed, 217 insertions, 9 deletions
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 6f4cdcef266a..be7c1e5c0333 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -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); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 38e331c4ac0d..d1063f647c4f 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -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. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index cdf66526ab63..592d1aa8f43f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index 3c644a5e1dc6..ea534bbd0794 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -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() @@ -153,6 +155,21 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { } @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) diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index f8954b7c7f95..b8c5b3f5524a 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -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); } diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index ae3ceb1203f8..933f00231313 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -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 @@ -2318,6 +2325,140 @@ public class PowerManagerServiceTest { } @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(); startSystem(); |