diff options
9 files changed, 246 insertions, 119 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 06be5fa5fb18..2de48fdac6df 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2560,6 +2560,17 @@ assistant activities (ACTIVITY_TYPE_ASSISTANT) --> <bool name="config_dismissDreamOnActivityStart">false</bool> + <!-- Whether to send a user activity event to PowerManager when a dream quits unexpectedly so + that the screen won't immediately shut off. + + When a dream stops unexpectedly, such as due to an app update, if the device has been + inactive less than the user's screen timeout, the device goes to keyguard and times out + back to dreaming after a few seconds. If the device has been inactive longer, the screen + will immediately turn off. With this flag on, the device will go back to keyguard in all + scenarios rather than turning off, which gives the device a chance to start dreaming + again. --> + <bool name="config_resetScreenTimeoutOnUnexpectedDreamExit">false</bool> + <!-- The prefixes of dream component names that are loggable. Matched against ComponentName#flattenToString() for dream components. If empty, logs "other" for all. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index dc4eafd2e00e..2d040bb66d83 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2207,6 +2207,7 @@ <java-symbol type="array" name="config_supportedDreamComplications" /> <java-symbol type="array" name="config_disabledDreamComponents" /> <java-symbol type="bool" name="config_dismissDreamOnActivityStart" /> + <java-symbol type="bool" name="config_resetScreenTimeoutOnUnexpectedDreamExit" /> <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" /> <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" /> <java-symbol type="integer" name="config_minDreamOverlayDurationMs" /> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 2d82c508dd1f..85cbcf13ed21 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -433,6 +433,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private final LockPatternUtils mLockPatternUtils; private final BroadcastDispatcher mBroadcastDispatcher; private boolean mKeyguardDonePending = false; + private boolean mUnlockingAndWakingFromDream = false; private boolean mHideAnimationRun = false; private boolean mHideAnimationRunning = false; @@ -806,6 +807,25 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false); mKeyguardDisplayManager.hide(); mUpdateMonitor.startBiometricWatchdog(); + + // It's possible that the device was unlocked (via BOUNCER or Fingerprint) while + // dreaming. It's time to wake up. + if (mUnlockingAndWakingFromDream) { + Log.d(TAG, "waking from dream after unlock"); + mUnlockingAndWakingFromDream = false; + + if (mKeyguardStateController.isShowing()) { + Log.d(TAG, "keyguard showing after keyguardGone, dismiss"); + mKeyguardViewControllerLazy.get() + .notifyKeyguardAuthenticated(!mWakeAndUnlocking); + } else { + Log.d(TAG, "keyguard gone, waking up from dream"); + mPM.wakeUp(SystemClock.uptimeMillis(), + mWakeAndUnlocking ? PowerManager.WAKE_REASON_BIOMETRIC + : PowerManager.WAKE_REASON_GESTURE, + "com.android.systemui:UNLOCK_DREAMING"); + } + } Trace.endSection(); } @@ -2634,6 +2654,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mKeyguardExitAnimationRunner = null; mWakeAndUnlocking = false; + mUnlockingAndWakingFromDream = false; setPendingLock(false); // Force if we we're showing in the middle of hiding, to ensure we end up in the correct @@ -2758,7 +2779,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mHiding = true; - if (mShowing && !mOccluded) { + mUnlockingAndWakingFromDream = mStatusBarStateController.isDreaming() + && !mStatusBarStateController.isDozing(); + + if ((mShowing && !mOccluded) || mUnlockingAndWakingFromDream) { + if (mUnlockingAndWakingFromDream) { + Log.d(TAG, "hiding keyguard before waking from dream"); + } mKeyguardGoingAwayRunnable.run(); } else { // TODO(bc-unlock): Fill parameters @@ -2769,13 +2796,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, null /* nonApps */, null /* finishedCallback */); }); } - - // It's possible that the device was unlocked (via BOUNCER or Fingerprint) while - // dreaming. It's time to wake up. - if (mDreamOverlayShowing) { - mPM.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, - "com.android.systemui:UNLOCK_DREAMING"); - } } Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 1f9c9f2b8c14..ccb51898a333 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -160,7 +160,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private KeyguardViewController mKeyguardViewController; private DozeScrimController mDozeScrimController; private KeyguardViewMediator mKeyguardViewMediator; - private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private PendingAuthenticated mPendingAuthenticated = null; private boolean mHasScreenTurnedOnSinceAuthenticating; private boolean mFadedAwayAfterWakeAndUnlock; @@ -281,8 +280,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp LatencyTracker latencyTracker, ScreenOffAnimationController screenOffAnimationController, VibratorHelper vibrator, - SystemClock systemClock, - StatusBarKeyguardViewManager statusBarKeyguardViewManager + SystemClock systemClock ) { mPowerManager = powerManager; mUpdateMonitor = keyguardUpdateMonitor; @@ -310,7 +308,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mVibratorHelper = vibrator; mLogger = biometricUnlockLogger; mSystemClock = systemClock; - mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; dumpManager.registerDumpable(getClass().getName(), this); } @@ -452,19 +449,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp // During wake and unlock, we need to draw black before waking up to avoid abrupt // brightness changes due to display state transitions. Runnable wakeUp = ()-> { - // Check to see if we are still locked when we are waking and unlocking from dream. - // This runnable should be executed after unlock. If that's true, we could be not - // dreaming, but still locked. In this case, we should attempt to authenticate instead - // of waking up. - if (mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM - && !mKeyguardStateController.isUnlocked() - && !mUpdateMonitor.isDreaming()) { - // Post wakeUp runnable is called from a callback in keyguard. - mHandler.post(() -> mKeyguardViewController.notifyKeyguardAuthenticated( - false /* primaryAuth */)); - } else if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) { + if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) { mLogger.i("bio wakelock: Authenticated, waking up..."); - mPowerManager.wakeUp( mSystemClock.uptimeMillis(), PowerManager.WAKE_REASON_BIOMETRIC, @@ -476,7 +462,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp Trace.endSection(); }; - if (mMode != MODE_NONE && mMode != MODE_WAKE_AND_UNLOCK_FROM_DREAM) { + final boolean wakingFromDream = mMode == MODE_WAKE_AND_UNLOCK_FROM_DREAM + && !mStatusBarStateController.isDozing(); + + if (mMode != MODE_NONE && !wakingFromDream) { wakeUp.run(); } switch (mMode) { @@ -498,10 +487,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp Trace.endSection(); break; case MODE_WAKE_AND_UNLOCK_FROM_DREAM: - // In the case of waking and unlocking from dream, waking up is delayed until after - // unlock is complete to avoid conflicts during each sequence's transitions. - mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(wakeUp); - // Execution falls through here to proceed unlocking. case MODE_WAKE_AND_UNLOCK_PULSING: case MODE_WAKE_AND_UNLOCK: if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 7a501a85714e..2fc3cf6af6b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -31,7 +31,10 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -598,6 +601,67 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { } @Test + public void testWakeAndUnlockingOverDream() { + // Send signal to wake + mViewMediator.onWakeAndUnlocking(); + + // Ensure not woken up yet + verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); + + // Verify keyguard told of authentication + verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); + mViewMediator.mViewMediatorCallback.keyguardDonePending(true, + mUpdateMonitor.getCurrentUser()); + mViewMediator.mViewMediatorCallback.readyForKeyguardDone(); + final ArgumentCaptor<Runnable> animationRunnableCaptor = + ArgumentCaptor.forClass(Runnable.class); + verify(mStatusBarKeyguardViewManager).startPreHideAnimation( + animationRunnableCaptor.capture()); + + when(mStatusBarStateController.isDreaming()).thenReturn(true); + when(mStatusBarStateController.isDozing()).thenReturn(false); + animationRunnableCaptor.getValue().run(); + + when(mKeyguardStateController.isShowing()).thenReturn(false); + mViewMediator.mViewMediatorCallback.keyguardGone(); + + // Verify woken up now. + verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString()); + } + + @Test + public void testWakeAndUnlockingOverDream_signalAuthenticateIfStillShowing() { + // Send signal to wake + mViewMediator.onWakeAndUnlocking(); + + // Ensure not woken up yet + verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); + + // Verify keyguard told of authentication + verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); + clearInvocations(mStatusBarKeyguardViewManager); + mViewMediator.mViewMediatorCallback.keyguardDonePending(true, + mUpdateMonitor.getCurrentUser()); + mViewMediator.mViewMediatorCallback.readyForKeyguardDone(); + final ArgumentCaptor<Runnable> animationRunnableCaptor = + ArgumentCaptor.forClass(Runnable.class); + verify(mStatusBarKeyguardViewManager).startPreHideAnimation( + animationRunnableCaptor.capture()); + + when(mStatusBarStateController.isDreaming()).thenReturn(true); + when(mStatusBarStateController.isDozing()).thenReturn(false); + animationRunnableCaptor.getValue().run(); + + when(mKeyguardStateController.isShowing()).thenReturn(true); + + mViewMediator.mViewMediatorCallback.keyguardGone(); + + + // Verify keyguard view controller informed of authentication again + verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); + } + + @Test @TestableLooper.RunWithLooper(setAsMainLooper = true) public void testDoKeyguardWhileInteractive_resets() { mViewMediator.setShowingLocked(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 479803e1dfac..4f8de3eacf7a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -142,8 +142,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle, mAuthController, mStatusBarStateController, mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper, - mSystemClock, - mStatusBarKeyguardViewManager + mSystemClock ); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); mBiometricUnlockController.addListener(mBiometricUnlockEventsListener); @@ -465,69 +464,6 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { } @Test - public void onSideFingerprintSuccess_dreaming_unlockThenWake() { - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - final ArgumentCaptor<Runnable> afterKeyguardGoneRunnableCaptor = - ArgumentCaptor.forClass(Runnable.class); - givenDreamingLocked(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true); - - // Make sure the BiometricUnlockController has registered a callback for when the keyguard - // is gone - verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable( - afterKeyguardGoneRunnableCaptor.capture()); - // Ensure that the power hasn't been told to wake up yet. - verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); - // Check that the keyguard has been told to unlock. - verify(mKeyguardViewMediator).onWakeAndUnlocking(); - - // Simulate the keyguard disappearing. - afterKeyguardGoneRunnableCaptor.getValue().run(); - // Verify that the power manager has been told to wake up now. - verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString()); - } - - @Test - public void onSideFingerprintSuccess_dreaming_unlockIfStillLockedNotDreaming() { - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - final ArgumentCaptor<Runnable> afterKeyguardGoneRunnableCaptor = - ArgumentCaptor.forClass(Runnable.class); - givenDreamingLocked(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true); - - // Make sure the BiometricUnlockController has registered a callback for when the keyguard - // is gone - verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable( - afterKeyguardGoneRunnableCaptor.capture()); - // Ensure that the power hasn't been told to wake up yet. - verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); - // Check that the keyguard has been told to unlock. - verify(mKeyguardViewMediator).onWakeAndUnlocking(); - - when(mUpdateMonitor.isDreaming()).thenReturn(false); - when(mKeyguardStateController.isUnlocked()).thenReturn(false); - - // Simulate the keyguard disappearing. - afterKeyguardGoneRunnableCaptor.getValue().run(); - - final ArgumentCaptor<Runnable> dismissKeyguardRunnableCaptor = - ArgumentCaptor.forClass(Runnable.class); - verify(mHandler).post(dismissKeyguardRunnableCaptor.capture()); - - // Verify that the power manager was not told to wake up. - verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); - - dismissKeyguardRunnableCaptor.getValue().run(); - // Verify that the keyguard controller is told to unlock. - verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false)); - } - - - @Test public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() { // GIVEN side fingerprint enrolled, last wake reason was power button when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); @@ -601,14 +537,25 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean()); } - private void givenDreamingLocked() { - when(mUpdateMonitor.isDreaming()).thenReturn(true); - when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); - } - private void givenFingerprintModeUnlockCollapsing() { when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true); when(mKeyguardStateController.isShowing()).thenReturn(true); } + + private void givenDreamingLocked() { + when(mUpdateMonitor.isDreaming()).thenReturn(true); + when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); + } + @Test + public void onSideFingerprintSuccess_dreaming_unlockNoWake() { + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); + givenDreamingLocked(); + mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true); + verify(mKeyguardViewMediator).onWakeAndUnlocking(); + // Ensure that the power hasn't been told to wake up yet. + verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); + } } diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index 6d70d21e3b84..da93d0b0fc35 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -18,6 +18,8 @@ package com.android.server.dreams; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; +import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER; +import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS; import android.app.ActivityTaskManager; import android.app.BroadcastOptions; @@ -72,6 +74,7 @@ final class DreamController { private final Handler mHandler; private final Listener mListener; private final ActivityTaskManager mActivityTaskManager; + private final PowerManager mPowerManager; private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | FLAG_RECEIVER_FOREGROUND); @@ -84,6 +87,15 @@ final class DreamController { private final Intent mCloseNotificationShadeIntent; private final Bundle mCloseNotificationShadeOptions; + /** + * If this flag is on, we report user activity to {@link PowerManager} so that the screen + * doesn't shut off immediately when a dream quits unexpectedly. The device will instead go to + * keyguard and time out back to dreaming shortly. + * + * This allows the dream a second chance to relaunch in case of an app update or other crash. + */ + private final boolean mResetScreenTimeoutOnUnexpectedDreamExit; + private DreamRecord mCurrentDream; // Whether a dreaming started intent has been broadcast. @@ -101,6 +113,7 @@ final class DreamController { mHandler = handler; mListener = listener; mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); + mPowerManager = mContext.getSystemService(PowerManager.class); mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); mCloseNotificationShadeIntent.putExtra(EXTRA_REASON_KEY, EXTRA_REASON_VALUE); mCloseNotificationShadeIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); @@ -110,6 +123,8 @@ final class DreamController { EXTRA_REASON_VALUE) .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) .toBundle(); + mResetScreenTimeoutOnUnexpectedDreamExit = context.getResources().getBoolean( + com.android.internal.R.bool.config_resetScreenTimeoutOnUnexpectedDreamExit); } /** @@ -214,6 +229,17 @@ final class DreamController { } /** + * Sends a user activity signal to PowerManager to stop the screen from turning off immediately + * if there hasn't been any user interaction in a while. + */ + private void resetScreenTimeout() { + Slog.i(TAG, "Resetting screen timeout"); + long time = SystemClock.uptimeMillis(); + mPowerManager.userActivity(time, USER_ACTIVITY_EVENT_OTHER, + USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS); + } + + /** * Stops dreaming. * * The current dream, if any, and any unstopped previous dreams are stopped. The device stops @@ -420,6 +446,9 @@ final class DreamController { mHandler.post(() -> { mService = null; if (mCurrentDream == DreamRecord.this) { + if (mResetScreenTimeoutOnUnexpectedDreamExit) { + resetScreenTimeout(); + } stopDream(true /*immediate*/, "binder died"); } }); @@ -445,6 +474,9 @@ final class DreamController { mHandler.post(() -> { mService = null; if (mCurrentDream == DreamRecord.this) { + if (mResetScreenTimeoutOnUnexpectedDreamExit) { + resetScreenTimeout(); + } stopDream(true /*immediate*/, "service disconnected"); } }); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 50f1673cae44..fbd54555dbbf 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -110,6 +110,7 @@ import android.app.backup.IBackupManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.DataLoaderType; @@ -119,7 +120,6 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; -import android.content.pm.ResolveInfo; import android.content.pm.SharedLibraryInfo; import android.content.pm.Signature; import android.content.pm.SigningDetails; @@ -184,7 +184,9 @@ import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedLibraryWrapper; import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.component.ParsedPermission; import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; @@ -207,6 +209,7 @@ import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -3925,23 +3928,6 @@ final class InstallPackageHelper { } } - // If this is a system app we hadn't seen before, and this is a first boot or OTA, - // we need to unstop it if it doesn't have a launcher entry. - if (mPm.mShouldStopSystemPackagesByDefault && scanResult.mRequest.mPkgSetting == null - && ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) - && ((scanFlags & SCAN_AS_SYSTEM) != 0)) { - final Intent launcherIntent = new Intent(Intent.ACTION_MAIN); - launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); - launcherIntent.setPackage(parsedPackage.getPackageName()); - final List<ResolveInfo> launcherActivities = - mPm.snapshotComputer().queryIntentActivitiesInternal(launcherIntent, null, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 0); - if (launcherActivities.isEmpty()) { - scanResult.mPkgSetting.setStopped(false, 0); - } - } - if (mIncrementalManager != null && isIncrementalPath(parsedPackage.getPath())) { if (scanResult.mPkgSetting != null && scanResult.mPkgSetting.isLoading()) { // Continue monitoring loading progress of active incremental packages @@ -4314,6 +4300,8 @@ final class InstallPackageHelper { // - It's an APEX or overlay package since stopped state does not affect them. // - It is enumerated with a <initial-package-state> tag having the stopped attribute // set to false + // - It doesn't have an enabled and exported launcher activity, which means the user + // wouldn't have a way to un-stop it final boolean isApexPkg = (scanFlags & SCAN_AS_APEX) != 0; if (mPm.mShouldStopSystemPackagesByDefault && scanSystemPartition @@ -4322,8 +4310,9 @@ final class InstallPackageHelper { && !parsedPackage.isOverlayIsStatic() ) { String packageName = parsedPackage.getPackageName(); - if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName) - && !"android".contentEquals(packageName)) { + if (!"android".contentEquals(packageName) + && !mPm.mInitialNonStoppedSystemPackages.contains(packageName) + && hasLauncherEntry(parsedPackage)) { scanFlags |= SCAN_AS_STOPPED_SYSTEM_APP; } } @@ -4333,6 +4322,26 @@ final class InstallPackageHelper { return new Pair<>(scanResult, shouldHideSystemApp); } + private static boolean hasLauncherEntry(ParsedPackage parsedPackage) { + final HashSet<String> categories = new HashSet<>(); + categories.add(Intent.CATEGORY_LAUNCHER); + final List<ParsedActivity> activities = parsedPackage.getActivities(); + for (int indexActivity = 0; indexActivity < activities.size(); indexActivity++) { + final ParsedActivity activity = activities.get(indexActivity); + if (!activity.isEnabled() || !activity.isExported()) { + continue; + } + final List<ParsedIntentInfo> intents = activity.getIntents(); + for (int indexIntent = 0; indexIntent < intents.size(); indexIntent++) { + final IntentFilter intentFilter = intents.get(indexIntent).getIntentFilter(); + if (intentFilter != null && intentFilter.matchCategories(categories) == null) { + return true; + } + } + } + return false; + } + /** * Returns if forced apk verification can be skipped for the whole package, including splits. */ diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java index d5ad815d3cdb..b5bf1ea34a46 100644 --- a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java @@ -16,7 +16,11 @@ package com.android.server.dreams; +import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER; +import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS; + import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; @@ -32,7 +36,9 @@ import android.content.ServiceConnection; import android.os.Binder; import android.os.Handler; import android.os.IBinder; +import android.os.IPowerManager; import android.os.IRemoteCallback; +import android.os.PowerManager; import android.os.RemoteException; import android.os.test.TestLooper; import android.service.dreams.IDreamService; @@ -58,6 +64,8 @@ public class DreamControllerTest { @Mock private ActivityTaskManager mActivityTaskManager; + @Mock + private IPowerManager mPowerManager; @Mock private IBinder mIBinder; @@ -67,6 +75,8 @@ public class DreamControllerTest { @Captor private ArgumentCaptor<ServiceConnection> mServiceConnectionACaptor; @Captor + private ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor; + @Captor private ArgumentCaptor<IRemoteCallback> mRemoteCallbackCaptor; private final TestLooper mLooper = new TestLooper(); @@ -90,6 +100,12 @@ public class DreamControllerTest { when(mContext.getSystemServiceName(ActivityTaskManager.class)) .thenReturn(Context.ACTIVITY_TASK_SERVICE); + final PowerManager powerManager = new PowerManager(mContext, mPowerManager, null, null); + when(mContext.getSystemService(Context.POWER_SERVICE)) + .thenReturn(powerManager); + when(mContext.getSystemServiceName(PowerManager.class)) + .thenReturn(Context.POWER_SERVICE); + mToken = new Binder(); mDreamName = ComponentName.unflattenFromString("dream"); mOverlayName = ComponentName.unflattenFromString("dream_overlay"); @@ -209,9 +225,51 @@ public class DreamControllerTest { verify(mIDreamService).detach(); } + @Test + public void serviceDisconnect_resetsScreenTimeout() throws RemoteException { + // Start dream. + mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, + 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); + ServiceConnection serviceConnection = captureServiceConnection(); + serviceConnection.onServiceConnected(mDreamName, mIBinder); + mLooper.dispatchAll(); + + // Dream disconnects unexpectedly. + serviceConnection.onServiceDisconnected(mDreamName); + mLooper.dispatchAll(); + + // Power manager receives user activity signal. + verify(mPowerManager).userActivity(/*displayId=*/ anyInt(), /*time=*/ anyLong(), + eq(USER_ACTIVITY_EVENT_OTHER), + eq(USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS)); + } + + @Test + public void binderDied_resetsScreenTimeout() throws RemoteException { + // Start dream. + mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, + 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); + captureServiceConnection().onServiceConnected(mDreamName, mIBinder); + mLooper.dispatchAll(); + + // Dream binder dies. + captureDeathRecipient().binderDied(); + mLooper.dispatchAll(); + + // Power manager receives user activity signal. + verify(mPowerManager).userActivity(/*displayId=*/ anyInt(), /*time=*/ anyLong(), + eq(USER_ACTIVITY_EVENT_OTHER), + eq(USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS)); + } + private ServiceConnection captureServiceConnection() { verify(mContext).bindServiceAsUser(any(), mServiceConnectionACaptor.capture(), anyInt(), any()); return mServiceConnectionACaptor.getValue(); } + + private IBinder.DeathRecipient captureDeathRecipient() throws RemoteException { + verify(mIBinder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt()); + return mDeathRecipientCaptor.getValue(); + } } |