diff options
229 files changed, 9144 insertions, 7338 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 0e4887d27b53..2ea8592e883e 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -2637,15 +2637,18 @@ public class AlarmManagerService extends SystemService { * Returns true if the given uid can set window to be as small as it wants. */ boolean isExemptFromMinWindowRestrictions(int uid) { - return isExemptFromExactAlarmPermission(uid); + return isExemptFromExactAlarmPermissionNoLock(uid); } /** * Returns true if the given uid does not require SCHEDULE_EXACT_ALARM to set exact, * allow-while-idle alarms. - * Note: It is ok to call this method without the lock {@link #mLock} held. + * <b> Note: This should not be called with {@link #mLock} held.</b> */ - boolean isExemptFromExactAlarmPermission(int uid) { + boolean isExemptFromExactAlarmPermissionNoLock(int uid) { + if (Build.IS_DEBUGGABLE && Thread.holdsLock(mLock)) { + Slog.wtfStack(TAG, "Alarm lock held while calling into DeviceIdleController"); + } return (UserHandle.isSameApp(mSystemUiUid, uid) || UserHandle.isCore(uid) || mLocalDeviceIdleController == null @@ -2747,7 +2750,7 @@ public class AlarmManagerService extends SystemService { } if (needsPermission && !hasScheduleExactAlarmInternal(callingPackage, callingUid) && !hasUseExactAlarmInternal(callingPackage, callingUid)) { - if (!isExemptFromExactAlarmPermission(callingUid)) { + if (!isExemptFromExactAlarmPermissionNoLock(callingUid)) { final String errorMessage = "Caller " + callingPackage + " needs to hold " + Manifest.permission.SCHEDULE_EXACT_ALARM + " to set " + "exact alarms."; @@ -2810,7 +2813,7 @@ public class AlarmManagerService extends SystemService { if (!isExactAlarmChangeEnabled(packageName, userId)) { return true; } - return isExemptFromExactAlarmPermission(packageUid) + return isExemptFromExactAlarmPermissionNoLock(packageUid) || hasScheduleExactAlarmInternal(packageName, packageUid) || hasUseExactAlarmInternal(packageName, packageUid); } @@ -3862,10 +3865,7 @@ public class AlarmManagerService extends SystemService { // added: true => package was added to the deny list // added: false => package was removed from the deny list if (added) { - synchronized (mLock) { - removeExactAlarmsOnPermissionRevokedLocked(uid, - changedPackage, /*killUid = */ true); - } + removeExactAlarmsOnPermissionRevoked(uid, changedPackage, /*killUid = */ true); } else { sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId); } @@ -3880,9 +3880,8 @@ public class AlarmManagerService extends SystemService { * * This is not expected to get called frequently. */ - @GuardedBy("mLock") - void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName, boolean killUid) { - if (isExemptFromExactAlarmPermission(uid) + void removeExactAlarmsOnPermissionRevoked(int uid, String packageName, boolean killUid) { + if (isExemptFromExactAlarmPermissionNoLock(uid) || !isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) { return; } @@ -3891,7 +3890,9 @@ public class AlarmManagerService extends SystemService { final Predicate<Alarm> whichAlarms = a -> (a.uid == uid && a.packageName.equals(packageName) && a.windowLength == 0); - removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED); + synchronized (mLock) { + removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED); + } if (killUid && mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) { PermissionManagerService.killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid), @@ -4807,10 +4808,7 @@ public class AlarmManagerService extends SystemService { case REMOVE_EXACT_ALARMS: int uid = msg.arg1; String packageName = (String) msg.obj; - synchronized (mLock) { - removeExactAlarmsOnPermissionRevokedLocked(uid, packageName, /*killUid = */ - true); - } + removeExactAlarmsOnPermissionRevoked(uid, packageName, /*killUid = */true); break; case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED: handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, true); @@ -4826,10 +4824,7 @@ public class AlarmManagerService extends SystemService { uid = msg.arg1; if (!hasScheduleExactAlarmInternal(packageName, uid) && !hasUseExactAlarmInternal(packageName, uid)) { - synchronized (mLock) { - removeExactAlarmsOnPermissionRevokedLocked(uid, - packageName, /*killUid = */false); - } + removeExactAlarmsOnPermissionRevoked(uid, packageName, /*killUid = */false); } break; case CHECK_EXACT_ALARM_PERMISSION_ON_FEATURE_TOGGLE: @@ -4849,10 +4844,7 @@ public class AlarmManagerService extends SystemService { if (defaultDenied) { if (!hasScheduleExactAlarmInternal(pkg, uid) && !hasUseExactAlarmInternal(pkg, uid)) { - synchronized (mLock) { - removeExactAlarmsOnPermissionRevokedLocked(uid, pkg, - true); - } + removeExactAlarmsOnPermissionRevoked(uid, pkg, true); } } else if (hasScheduleExactAlarmInternal(pkg, uid)) { sendScheduleExactAlarmPermissionStateChangedBroadcast(pkg, diff --git a/api/api.go b/api/api.go index 9aac879b4eae..ce8cd1426661 100644 --- a/api/api.go +++ b/api/api.go @@ -208,12 +208,6 @@ func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) { props := libraryProps{} props.Name = proptools.StringPtr("all-framework-module-impl") props.Static_libs = transformArray(modules, "", ".impl") - // Media module's impl jar is called "updatable-media" - for i, v := range props.Static_libs { - if v == "framework-media.impl" { - props.Static_libs[i] = "updatable-media" - } - } props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} ctx.CreateModule(java.LibraryFactory, &props) diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index add891d40d95..b49e571f74e1 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -374,10 +374,6 @@ public class PropertyInvalidatedCache<Query, Result> { private static final String TAG = "PropertyInvalidatedCache"; private static final boolean DEBUG = false; private static final boolean VERIFY = false; - // If this is true, dumpsys will dump the cache entries along with cache statistics. - // Most of the time this causes dumpsys to fail because the output stream is too - // large. Only set it to true in development images. - private static final boolean DETAILED = false; // Per-Cache performance counters. As some cache instances are declared static, @GuardedBy("mLock") @@ -1358,7 +1354,69 @@ public class PropertyInvalidatedCache<Query, Result> { } } - private void dumpContents(PrintWriter pw) { + /** + * Switches that can be used to control the detail emitted by a cache dump. The + * "CONTAINS" switches match if the cache (property) name contains the switch + * argument. The "LIKE" switches match if the cache (property) name matches the + * switch argument as a regex. The regular expression must match the entire name, + * which generally means it may need leading/trailing "." expressions. + */ + final static String NAME_CONTAINS = "-name-has="; + final static String NAME_LIKE = "-name-like="; + final static String PROPERTY_CONTAINS = "-property-has="; + final static String PROPERTY_LIKE = "-property-like="; + + /** + * Return true if any argument is a detailed specification switch. + */ + private static boolean anyDetailed(String[] args) { + for (String a : args) { + if (a.startsWith(NAME_CONTAINS) || a.startsWith(NAME_LIKE) + || a.startsWith(PROPERTY_CONTAINS) || a.startsWith(PROPERTY_LIKE)) { + return true; + } + } + return false; + } + + /** + * A helper method to determine if a string matches a switch. + */ + private static boolean chooses(String arg, String key, String reference, boolean contains) { + if (arg.startsWith(key)) { + final String value = arg.substring(key.length()); + if (contains) { + return reference.contains(value); + } else { + return reference.matches(value); + } + } + return false; + } + + /** + * Return true if this cache should be dumped in detail. This method is not called + * unless it has already been determined that there is at least one match requested. + */ + private boolean showDetailed(String[] args) { + for (String a : args) { + if (chooses(a, NAME_CONTAINS, cacheName(), true) + || chooses(a, NAME_LIKE, cacheName(), false) + || chooses(a, PROPERTY_CONTAINS, mPropertyName, true) + || chooses(a, PROPERTY_LIKE, mPropertyName, false)) { + return true; + } + } + return false; + } + + private void dumpContents(PrintWriter pw, boolean detailed, String[] args) { + // If the user has requested specific caches and this is not one of them, return + // immediately. + if (detailed && !showDetailed(args)) { + return; + } + long invalidateCount; long corkedInvalidates; synchronized (sCorkLock) { @@ -1386,9 +1444,15 @@ public class PropertyInvalidatedCache<Query, Result> { mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow)); pw.println(TextUtils.formatSimple(" Enabled: %s", mDisabled ? "false" : "true")); pw.println(""); + pw.flush(); + // No specific cache was requested. This is the default, and no details + // should be dumped. + if (!detailed) { + return; + } Set<Map.Entry<Query, Result>> cacheEntries = mCache.entrySet(); - if (!DETAILED || cacheEntries.size() == 0) { + if (cacheEntries.size() == 0) { return; } @@ -1399,17 +1463,34 @@ public class PropertyInvalidatedCache<Query, Result> { pw.println(TextUtils.formatSimple(" Key: %s\n Value: %s\n", key, value)); } + pw.flush(); + } + } + + /** + * Dump the corking status. + */ + @GuardedBy("sCorkLock") + private static void dumpCorkInfo(PrintWriter pw) { + ArrayList<Map.Entry<String, Integer>> activeCorks = getActiveCorks(); + if (activeCorks.size() > 0) { + pw.println(" Corking Status:"); + for (int i = 0; i < activeCorks.size(); i++) { + Map.Entry<String, Integer> entry = activeCorks.get(i); + pw.println(TextUtils.formatSimple(" Property Name: %s Count: %d", + entry.getKey(), entry.getValue())); + } } } /** - * Dumps contents of every cache in the process to the provided ParcelFileDescriptor. + * Without arguments, this dumps statistics from every cache in the process to the + * provided ParcelFileDescriptor. Optional switches allow the caller to choose + * specific caches (selection is by cache name or property name); if these switches + * are used then the output includes both cache statistics and cache entries. * @hide */ public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) { - ArrayList<PropertyInvalidatedCache> activeCaches; - ArrayList<Map.Entry<String, Integer>> activeCorks; - try ( FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor()); PrintWriter pw = new FastPrintWriter(fout); @@ -1419,24 +1500,21 @@ public class PropertyInvalidatedCache<Query, Result> { return; } + // See if detailed is requested for any cache. If there is a specific detailed request, + // then only that cache is reported. + boolean detail = anyDetailed(args); + + ArrayList<PropertyInvalidatedCache> activeCaches; synchronized (sCorkLock) { activeCaches = getActiveCaches(); - activeCorks = getActiveCorks(); - - if (activeCorks.size() > 0) { - pw.println(" Corking Status:"); - for (int i = 0; i < activeCorks.size(); i++) { - Map.Entry<String, Integer> entry = activeCorks.get(i); - pw.println(TextUtils.formatSimple(" Property Name: %s Count: %d", - entry.getKey(), entry.getValue())); - } + if (!detail) { + dumpCorkInfo(pw); } } for (int i = 0; i < activeCaches.size(); i++) { PropertyInvalidatedCache currentCache = activeCaches.get(i); - currentCache.dumpContents(pw); - pw.flush(); + currentCache.dumpContents(pw, detail, args); } } catch (IOException e) { Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances"); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 4d4a57db84be..44dc28d2b0fa 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1414,11 +1414,9 @@ public class PackageParser { final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); final ParseResult<android.content.pm.SigningDetails> result; if (skipVerify) { - // systemDir APKs are already trusted, save time by not verifying; since the signature - // is not verified and some system apps can have their V2+ signatures stripped allow - // pulling the certs from the jar signature. + // systemDir APKs are already trusted, save time by not verifying result = ApkSignatureVerifier.unsafeGetCertsWithoutVerification( - input, apkPath, SigningDetails.SignatureSchemeVersion.JAR); + input, apkPath, minSignatureScheme); } else { result = ApkSignatureVerifier.verify(input, apkPath, minSignatureScheme); } diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index cc62d5337c02..1c1f58a19abc 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -168,15 +168,6 @@ "name": "CtsIncrementalInstallHostTestCases" }, { - "name": "CtsInstallHostTestCases" - }, - { - "name": "CtsStagedInstallHostTestCases" - }, - { - "name": "CtsExtractNativeLibsHostTestCases" - }, - { "name": "CtsAppSecurityHostTestCases", "options": [ { @@ -188,34 +179,47 @@ ] }, { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.pm.PackageParserTest" - } - ] - }, - { - "name": "CtsRollbackManagerHostTestCases" - }, - { "name": "CtsContentTestCases", "options": [ { "include-filter": "android.content.cts.IntentFilterTest" } ] - }, - { - "name": "CtsAppEnumerationTestCases" - }, - { - "name": "PackageManagerServiceUnitTests", - "options": [ - { - "include-filter": "com.android.server.pm.test.verify.domain" - } - ] } + ], + "platinum-postsubmit": [ + { + "name": "CtsIncrementalInstallHostTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "CtsAppSecurityHostTestCases", + "options": [ + { + "include-filter": "android.appsecurity.cts.SplitTests" + }, + { + "include-filter": "android.appsecurity.cts.EphemeralTest" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "CtsContentTestCases", + "options":[ + { + "include-filter": "android.content.cts.IntentFilterTest" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } ] } diff --git a/core/java/android/content/pm/verify/domain/TEST_MAPPING b/core/java/android/content/pm/verify/domain/TEST_MAPPING index ba4a62cdbbf1..8a1982a339ea 100644 --- a/core/java/android/content/pm/verify/domain/TEST_MAPPING +++ b/core/java/android/content/pm/verify/domain/TEST_MAPPING @@ -12,9 +12,6 @@ "name": "CtsDomainVerificationDeviceStandaloneTestCases" }, { - "name": "CtsDomainVerificationDeviceMultiUserTestCases" - }, - { "name": "CtsDomainVerificationHostTestCases" } ] diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index 39cb7f3ebddb..bb0caa787e27 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -224,9 +224,10 @@ public final class OutputConfiguration implements Parcelable { * display subsystem for smoother display of camera frames. An output target of SurfaceView * uses this time base by default.</p> * - * <p>The choreographer synchronized timestamps are also reasonable to use when drawing to a - * TextureView. So this timestamp base can be used for a SurfaceTexture as part of a - * TextureView, in addition to SurfaceView.</p> + * <p>This timestamp base isn't applicable to SurfaceTexture targets. SurfaceTexture's + * {@link android.graphics.SurfaceTexture#updateTexImage updateTexImage} function always + * uses the latest image from the camera stream. In the case of a TextureView, the image is + * displayed right away.</p> * * <p>Timestamps with this time base cannot directly match the timestamps in * {@link CameraCaptureSession.CaptureCallback#onCaptureStarted} or the sensor timestamps in diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index a6ed42348af6..efd4f0681838 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -482,8 +482,9 @@ public class InputMethodService extends AbstractInputMethodService { /** * Timeout after which hidden IME surface will be removed from memory + * TODO(b/230762351): reset timeout to 5000ms and invalidate cache when IME insets change. */ - private static final long TIMEOUT_SURFACE_REMOVAL_MILLIS = 5000; + private static final long TIMEOUT_SURFACE_REMOVAL_MILLIS = 500; InputMethodManager mImm; private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations(); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 34647b144be1..20a2bdf3b109 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7679,20 +7679,6 @@ public final class Settings { "zen_settings_suggestion_viewed"; /** - * State of whether review notification permissions notification needs to - * be shown the user, and whether the user has interacted. - * - * Valid values: - * -1 = UNKNOWN - * 0 = SHOULD_SHOW - * 1 = USER_INTERACTED - * 2 = DISMISSED - * @hide - */ - public static final String REVIEW_PERMISSIONS_NOTIFICATION_STATE = - "review_permissions_notification_state"; - - /** * Whether the in call notification is enabled to play sound during calls. The value is * boolean (1 or 0). * @hide @@ -9696,6 +9682,26 @@ public final class Settings { public static final String BIOMETRIC_APP_ENABLED = "biometric_app_enabled"; /** + * Whether or not active unlock triggers on wake. + * @hide + */ + public static final String ACTIVE_UNLOCK_ON_WAKE = "active_unlock_on_wake"; + + /** + * Whether or not active unlock triggers on unlock intent. + * @hide + */ + public static final String ACTIVE_UNLOCK_ON_UNLOCK_INTENT = + "active_unlock_on_unlock_intent"; + + /** + * Whether or not active unlock triggers on biometric failure. + * @hide + */ + public static final String ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL = + "active_unlock_on_biometric_fail"; + + /** * Whether the assist gesture should be enabled. * * @hide @@ -16966,6 +16972,21 @@ public final class Settings { "managed_provisioning_defer_provisioning_to_role_holder"; /** + * State of whether review notification permissions notification needs to + * be shown the user, and whether the user has interacted. + * + * Valid values: + * -1 = UNKNOWN + * 0 = SHOULD_SHOW + * 1 = USER_INTERACTED + * 2 = DISMISSED + * 3 = RESHOWN + * @hide + */ + public static final String REVIEW_PERMISSIONS_NOTIFICATION_STATE = + "review_permissions_notification_state"; + + /** * Settings migrated from Wear OS settings provider. * @hide */ diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 6a9afdb84e18..47fc120c9d4f 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -999,6 +999,14 @@ public class DreamService extends Service implements Window.Callback { return mDreamServiceWrapper; } + @Override + public boolean onUnbind(Intent intent) { + // We must unbind from any overlay connection if we are unbound before finishing. + mOverlayConnection.unbind(this); + + return super.onUnbind(intent); + } + /** * Stops the dream and detaches from the window. * <p> diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 77591a7efb5e..ebbe64c396e7 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -785,9 +785,7 @@ public final class Choreographer { } } frameTimeNanos = startNanos - lastFrameOffset; - DisplayEventReceiver.VsyncEventData latestVsyncEventData = - mDisplayEventReceiver.getLatestVsyncEventData(); - frameData.updateFrameData(frameTimeNanos, latestVsyncEventData); + frameData.updateFrameData(frameTimeNanos); } if (frameTimeNanos < mLastFrameTimeNanos) { @@ -885,9 +883,7 @@ public final class Choreographer { } frameTimeNanos = now - lastFrameOffset; mLastFrameTimeNanos = frameTimeNanos; - DisplayEventReceiver.VsyncEventData latestVsyncEventData = - mDisplayEventReceiver.getLatestVsyncEventData(); - frameData.updateFrameData(frameTimeNanos, latestVsyncEventData); + frameData.updateFrameData(frameTimeNanos); } } } @@ -1022,6 +1018,11 @@ public final class Choreographer { return mVsyncId; } + /** Reset the vsync ID to invalid. */ + void resetVsyncId() { + mVsyncId = FrameInfo.INVALID_VSYNC_ID; + } + /** * The time in {@link System#nanoTime()} timebase which this frame is expected to be * presented. @@ -1069,12 +1070,14 @@ public final class Choreographer { private FrameTimeline[] mFrameTimelines; private FrameTimeline mPreferredFrameTimeline; - void updateFrameData(long frameTimeNanos, - DisplayEventReceiver.VsyncEventData latestVsyncEventData) { + void updateFrameData(long frameTimeNanos) { mFrameTimeNanos = frameTimeNanos; - mFrameTimelines = convertFrameTimelines(latestVsyncEventData); - mPreferredFrameTimeline = - mFrameTimelines[latestVsyncEventData.preferredFrameTimelineIndex]; + for (FrameTimeline ft : mFrameTimelines) { + // The ID is no longer valid because the frame time that was registered with the ID + // no longer matches. + // TODO(b/205721584): Ask SF for valid vsync information. + ft.resetVsyncId(); + } } /** The time in nanoseconds when the frame started being rendered. */ diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 4fdea3b006dc..332e97c8bcf5 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -65,6 +65,9 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { public void onWindowFocusGained(boolean hasViewFocus) { super.onWindowFocusGained(hasViewFocus); getImm().registerImeConsumer(this); + if (isRequestedVisible() && getControl() == null) { + mIsRequestedVisibleAwaitingControl = true; + } } @Override @@ -149,14 +152,11 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { } @Override - public void setControl(@Nullable InsetsSourceControl control, int[] showTypes, + public boolean setControl(@Nullable InsetsSourceControl control, int[] showTypes, int[] hideTypes) { - super.setControl(control, showTypes, hideTypes); - // TODO(b/204524304): clean-up how to deal with the timing issues of hiding IME: - // 1) Already requested show IME, in the meantime of WM callback the control but got null - // control when relayout comes first - // 2) Make sure no regression on some implicit request IME visibility calls (e.g. - // toggleSoftInput) + if (!super.setControl(control, showTypes, hideTypes)) { + return false; + } if (control == null && !mIsRequestedVisibleAwaitingControl) { hide(); removeSurface(); @@ -164,6 +164,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { if (control != null) { mIsRequestedVisibleAwaitingControl = false; } + return true; } @Override diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 4d9033df89e1..d6b75b94b19a 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -122,8 +122,9 @@ public class InsetsSourceConsumer { * animation should be run after setting the control. * @param hideTypes An integer array with a single entry that determines which types a hide * animation should be run after setting the control. + * @return Whether the control has changed from the server */ - public void setControl(@Nullable InsetsSourceControl control, + public boolean setControl(@Nullable InsetsSourceControl control, @InsetsType int[] showTypes, @InsetsType int[] hideTypes) { if (mType == ITYPE_IME) { ImeTracing.getInstance().triggerClientDump("InsetsSourceConsumer#setControl", @@ -134,7 +135,7 @@ public class InsetsSourceConsumer { mSourceControl.release(SurfaceControl::release); mSourceControl = control; } - return; + return false; } SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null; @@ -201,6 +202,7 @@ public class InsetsSourceConsumer { if (lastControl != null) { lastControl.release(SurfaceControl::release); } + return true; } @VisibleForTesting diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index b5bbc7537391..a1ce39e974e3 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -785,12 +785,14 @@ public final class SurfaceControl implements Parcelable { private final HardwareBuffer mHardwareBuffer; private final ColorSpace mColorSpace; private final boolean mContainsSecureLayers; + private final boolean mContainsHdrLayers; public ScreenshotHardwareBuffer(HardwareBuffer hardwareBuffer, ColorSpace colorSpace, - boolean containsSecureLayers) { + boolean containsSecureLayers, boolean containsHdrLayers) { mHardwareBuffer = hardwareBuffer; mColorSpace = colorSpace; mContainsSecureLayers = containsSecureLayers; + mContainsHdrLayers = containsHdrLayers; } /** @@ -798,13 +800,15 @@ public final class SurfaceControl implements Parcelable { * @param hardwareBuffer The existing HardwareBuffer object * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named} * @param containsSecureLayers Indicates whether this graphic buffer contains captured - * contents - * of secure layers, in which case the screenshot should not be persisted. + * contents of secure layers, in which case the screenshot + * should not be persisted. + * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content. */ private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer, - int namedColorSpace, boolean containsSecureLayers) { + int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) { ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]); - return new ScreenshotHardwareBuffer(hardwareBuffer, colorSpace, containsSecureLayers); + return new ScreenshotHardwareBuffer( + hardwareBuffer, colorSpace, containsSecureLayers, containsHdrLayers); } public ColorSpace getColorSpace() { @@ -818,6 +822,14 @@ public final class SurfaceControl implements Parcelable { public boolean containsSecureLayers() { return mContainsSecureLayers; } + /** + * Returns whether the screenshot contains at least one HDR layer. + * This information may be useful for informing the display whether this screenshot + * is allowed to be dimmed to SDR white. + */ + public boolean containsHdrLayers() { + return mContainsHdrLayers; + } /** * Copy content of ScreenshotHardwareBuffer into a hardware bitmap and return it. @@ -3871,8 +3883,8 @@ public final class SurfaceControl implements Parcelable { @Deprecated public Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) { checkPreconditions(sc); - if (colorSpace.getId() == ColorSpace.Named.DCI_P3.ordinal()) { - setDataSpace(sc, DataSpace.DATASPACE_DCI_P3); + if (colorSpace.getId() == ColorSpace.Named.DISPLAY_P3.ordinal()) { + setDataSpace(sc, DataSpace.DATASPACE_DISPLAY_P3); } else { setDataSpace(sc, DataSpace.DATASPACE_SRGB); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fbb86ff3a55a..b7a2aa0b0174 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -5035,9 +5035,6 @@ public final class ViewRootImpl implements ViewParent, } void requestPointerCapture(boolean enabled) { - if (mPointerCapture == enabled) { - return; - } final IBinder inputToken = getInputToken(); if (inputToken == null) { Log.e(mTag, "No input channel to request Pointer Capture."); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index cfe44bbbf3c6..5bc340b76f56 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -488,6 +488,13 @@ public interface WindowManager extends ViewManager { int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION = 0x8; /** + * Transition flag: Keyguard is going away to the launcher, and it needs us to clear the task + * snapshot of the launcher because it has changed something in the Launcher window. + * @hide + */ + int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT = 0x16; + + /** * Transition flag: App is crashed. * @hide */ @@ -527,6 +534,7 @@ public interface WindowManager extends ViewManager { TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION, TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER, TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION, + TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT, TRANSIT_FLAG_APP_CRASHED, TRANSIT_FLAG_OPEN_BEHIND, TRANSIT_FLAG_KEYGUARD_LOCKED, @@ -717,8 +725,8 @@ public interface WindowManager extends ViewManager { /** * Returns a set of {@link WindowMetrics} for the given display. Each WindowMetrics instance - * is the maximum WindowMetrics for a device state, including rotations. This is not guaranteed - * to include all possible device states. + * is the maximum WindowMetrics for a device state. This is not guaranteed to include all + * possible device states. * * This API can only be used by Launcher. * diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index 94f633314b4e..4d07171d3086 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -48,6 +48,7 @@ public interface WindowManagerPolicyConstants { int KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS = 1 << 1; int KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER = 1 << 2; int KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS = 1 << 3; + int KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT = 1 << 4; // Flags used for indicating whether the internal and/or external input devices // of some type are available. diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 850256871b15..d9bde5825fde 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -33,6 +33,7 @@ import static android.view.inputmethod.InputMethodManagerProto.CUR_ID; import static android.view.inputmethod.InputMethodManagerProto.FULLSCREEN_MODE; import static android.view.inputmethod.InputMethodManagerProto.SERVED_CONNECTING; +import static com.android.internal.inputmethod.StartInputReason.BOUND_TO_IMMS; import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION; import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION; @@ -450,6 +451,11 @@ public final class InputMethodManager { int mInitialSelEnd; /** + * Handler for {@link RemoteInputConnectionImpl#getInputConnection()}. + */ + private Handler mServedInputConnectionHandler; + + /** * The instance that has previously been sent to the input method. */ private CursorAnchorInfo mCursorAnchorInfo = null; @@ -1658,6 +1664,7 @@ public final class InputMethodManager { if (mServedInputConnection != null) { mServedInputConnection.deactivate(); mServedInputConnection = null; + mServedInputConnectionHandler = null; } } @@ -2289,6 +2296,13 @@ public final class InputMethodManager { "Starting input: finished by someone else. view=" + dumpViewInfo(view) + " servedView=" + dumpViewInfo(servedView) + " mServedConnecting=" + mServedConnecting); + if (mServedInputConnection != null && startInputReason == BOUND_TO_IMMS) { + // This is not an error. Once IME binds (MSG_BIND), InputConnection is fully + // established. So we report this to interested recipients. + reportInputConnectionOpened( + mServedInputConnection.getInputConnection(), mCurrentTextBoxAttribute, + mServedInputConnectionHandler, view); + } return false; } @@ -2305,6 +2319,7 @@ public final class InputMethodManager { if (mServedInputConnection != null) { mServedInputConnection.deactivate(); mServedInputConnection = null; + mServedInputConnectionHandler = null; } RemoteInputConnectionImpl servedInputConnection; if (ic != null) { @@ -2323,11 +2338,13 @@ public final class InputMethodManager { // TODO(b/199934664): See if we can remove this by providing a default impl. } icHandler = handler; + mServedInputConnectionHandler = icHandler; servedInputConnection = new RemoteInputConnectionImpl( icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this, view); } else { servedInputConnection = null; icHandler = null; + mServedInputConnectionHandler = null; } mServedInputConnection = servedInputConnection; @@ -2397,16 +2414,21 @@ public final class InputMethodManager { Log.v(TAG, "Calling View.onInputConnectionOpened: view= " + view + ", ic=" + ic + ", tba=" + tba + ", handler=" + icHandler); } - view.onInputConnectionOpenedInternal(ic, tba, icHandler); - final ViewRootImpl viewRoot = view.getViewRootImpl(); - if (viewRoot != null) { - viewRoot.getHandwritingInitiator().onInputConnectionCreated(view); - } + reportInputConnectionOpened(ic, tba, icHandler, view); } return true; } + private void reportInputConnectionOpened( + InputConnection ic, EditorInfo tba, Handler icHandler, View view) { + view.onInputConnectionOpenedInternal(ic, tba, icHandler); + final ViewRootImpl viewRoot = view.getViewRootImpl(); + if (viewRoot != null) { + viewRoot.getHandwritingInitiator().onInputConnectionCreated(view); + } + } + /** * An empty method only to avoid crashes of apps that call this method via reflection and do not * handle {@link NoSuchMethodException} in a graceful manner. @@ -3550,6 +3572,7 @@ public final class InputMethodManager { p.println(" mCurrentTextBoxAttribute: null"); } p.println(" mServedInputConnection=" + mServedInputConnection); + p.println(" mServedInputConnectionHandler=" + mServedInputConnectionHandler); p.println(" mCompletions=" + Arrays.toString(mCompletions)); p.println(" mCursorRect=" + mCursorRect); p.println(" mCursorSelStart=" + mCursorSelStart diff --git a/core/java/com/android/internal/app/AppLocaleStore.java b/core/java/com/android/internal/app/AppLocaleStore.java index f95838516927..599e6d24600c 100644 --- a/core/java/com/android/internal/app/AppLocaleStore.java +++ b/core/java/com/android/internal/app/AppLocaleStore.java @@ -51,13 +51,17 @@ class AppLocaleStore { appSupportedLocales.add(packageLocaleList.get(i)); } } else { - localeStatus = LocaleStatus.NO_SUPPORTED_LANGUAGE; + localeStatus = LocaleStatus.NO_SUPPORTED_LANGUAGE_IN_APP; } } else if (localeConfig.getStatus() == LocaleConfig.STATUS_NOT_SPECIFIED) { - localeStatus = LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET; String[] languages = getAssetLocales(context, packageName); - for (String language : languages) { - appSupportedLocales.add(Locale.forLanguageTag(language)); + if (languages.length > 0) { + localeStatus = LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET; + for (String language : languages) { + appSupportedLocales.add(Locale.forLanguageTag(language)); + } + } else { + localeStatus = LocaleStatus.ASSET_LOCALE_IS_EMPTY; } } } @@ -89,7 +93,8 @@ class AppLocaleStore { static class AppLocaleResult { enum LocaleStatus { UNKNOWN_FAILURE, - NO_SUPPORTED_LANGUAGE, + NO_SUPPORTED_LANGUAGE_IN_APP, + ASSET_LOCALE_IS_EMPTY, GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG, GET_SUPPORTED_LANGUAGE_FROM_ASSET, } diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java index 12b522bb98f2..f21cee997163 100644 --- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java @@ -191,7 +191,7 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { * @return {@link InputConnection} to which incoming IPCs will be dispatched. */ @Nullable - private InputConnection getInputConnection() { + public InputConnection getInputConnection() { synchronized (mLock) { return mInputConnection; } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 51a708b76801..c769da57eecc 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -322,7 +322,8 @@ public: env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz, gScreenshotHardwareBufferClassInfo.builder, jhardwareBuffer, namedColorSpace, - captureResults.capturedSecureLayers); + captureResults.capturedSecureLayers, + captureResults.capturedHdrLayers); env->CallVoidMethod(screenCaptureListenerObject, gScreenCaptureListenerClassInfo.onScreenCaptureComplete, screenshotHardwareBuffer); @@ -2399,7 +2400,7 @@ int register_android_view_SurfaceControl(JNIEnv* env) MakeGlobalRefOrDie(env, screenshotGraphicsBufferClazz); gScreenshotHardwareBufferClassInfo.builder = GetStaticMethodIDOrDie(env, screenshotGraphicsBufferClazz, "createFromNative", - "(Landroid/hardware/HardwareBuffer;IZ)Landroid/view/" + "(Landroid/hardware/HardwareBuffer;IZZ)Landroid/view/" "SurfaceControl$ScreenshotHardwareBuffer;"); jclass displayedContentSampleClazz = FindClassOrDie(env, diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 19b72bfbe6c0..edaf8cf279e3 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2415,8 +2415,12 @@ <!-- Is the system user the only user allowed to dream. --> <bool name="config_dreamsOnlyEnabledForSystemUser">false</bool> + <!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to + assistant activities (ACTIVITY_TYPE_ASSISTANT) --> + <bool name="config_dismissDreamOnActivityStart">true</bool> + <!-- The prefix of dream component names that are loggable. If empty, logs "other" for all. --> - <string name ="config_loggable_dream_prefix" translatable="false"></string> + <string name="config_loggable_dream_prefix" translatable="false"></string> <!-- ComponentName of a dream to show whenever the system would otherwise have gone to sleep. When the PowerManager is asked to go to sleep, it will instead diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 012030e9b393..8226ec435533 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2221,6 +2221,7 @@ <java-symbol type="array" name="config_supportedDreamComplications" /> <java-symbol type="array" name="config_dreamComplicationsEnabledByDefault" /> <java-symbol type="array" name="config_disabledDreamComponents" /> + <java-symbol type="bool" name="config_dismissDreamOnActivityStart" /> <java-symbol type="string" name="config_loggable_dream_prefix" /> <java-symbol type="string" name="config_dozeComponent" /> <java-symbol type="string" name="enable_explore_by_touch_warning_title" /> diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 81caf7786cf5..e50b9a1cd469 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -16,6 +16,7 @@ package androidx.window.extensions.embedding; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.Activity; @@ -48,8 +49,7 @@ import java.util.concurrent.Executor; class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */ - @VisibleForTesting - final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>(); + private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>(); /** * Mapping from the client assigned unique token to the TaskFragment parent @@ -120,29 +120,25 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { * @param secondaryFragmentBounds the initial bounds for the secondary TaskFragment * @param activityIntent Intent to start the secondary Activity with. * @param activityOptions ActivityOptions to start the secondary Activity with. - * @param windowingMode the windowing mode to set for the TaskFragments. */ void startActivityToSide(@NonNull WindowContainerTransaction wct, @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds, @NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken, @NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent, - @Nullable Bundle activityOptions, @NonNull SplitRule rule, - @WindowingMode int windowingMode) { + @Nullable Bundle activityOptions, @NonNull SplitRule rule) { final IBinder ownerToken = launchingActivity.getActivityToken(); // Create or resize the launching TaskFragment. if (mFragmentInfos.containsKey(launchingFragmentToken)) { resizeTaskFragment(wct, launchingFragmentToken, launchingFragmentBounds); - wct.setWindowingMode(mFragmentInfos.get(launchingFragmentToken).getToken(), - windowingMode); } else { createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken, - launchingFragmentBounds, windowingMode, launchingActivity); + launchingFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, launchingActivity); } // Create a TaskFragment for the secondary activity. createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken, - secondaryFragmentBounds, windowingMode, activityIntent, + secondaryFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, activityIntent, activityOptions); // Set adjacent to each other so that the containers below will be invisible. @@ -157,7 +153,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) { resizeTaskFragment(wct, fragmentToken, new Rect()); setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */); - setWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED); } /** @@ -260,15 +255,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds); } - private void setWindowingMode(WindowContainerTransaction wct, IBinder fragmentToken, - @WindowingMode int windowingMode) { - if (!mFragmentInfos.containsKey(fragmentToken)) { - throw new IllegalArgumentException( - "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken); - } - wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode); - } - void deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) { if (!mFragmentInfos.containsKey(fragmentToken)) { throw new IllegalArgumentException( diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index b370e59ac7c8..2328f76a7130 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -257,9 +257,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (taskContainer == null) { return; } - final boolean wasInPip = taskContainer.isInPictureInPicture(); + final boolean wasInPip = isInPictureInPicture(taskContainer.getConfiguration()); final boolean isInPIp = isInPictureInPicture(config); - taskContainer.setWindowingMode(config.windowConfiguration.getWindowingMode()); + taskContainer.setConfiguration(config); // We need to check the animation override when enter/exit PIP or has bounds changed. boolean shouldUpdateAnimationOverride = wasInPip != isInPIp; @@ -278,9 +278,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * bounds is large enough for at least one split rule. */ private void updateAnimationOverride(@NonNull TaskContainer taskContainer) { - if (!taskContainer.isTaskBoundsInitialized() - || !taskContainer.isWindowingModeInitialized()) { - // We don't know about the Task bounds/windowingMode yet. + if (!taskContainer.isTaskBoundsInitialized()) { + // We don't know about the Task bounds yet. return; } @@ -294,7 +293,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private boolean supportSplit(@NonNull TaskContainer taskContainer) { // No split inside PIP. - if (taskContainer.isInPictureInPicture()) { + if (isInPictureInPicture(taskContainer.getConfiguration())) { return false; } // Check if the parent container bounds can support any split rule. @@ -462,12 +461,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (!taskContainer.setTaskBounds(taskBounds)) { Log.w(TAG, "Can't find bounds from activity=" + activityInTask); } + updateAnimationOverride(taskContainer); } - if (!taskContainer.isWindowingModeInitialized()) { - taskContainer.setWindowingMode(activityInTask.getResources().getConfiguration() - .windowConfiguration.getWindowingMode()); - } - updateAnimationOverride(taskContainer); return container; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index e64e5d1c66d5..ee5a322eed4f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -16,11 +16,10 @@ package androidx.window.extensions.embedding; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import android.app.Activity; import android.app.WindowConfiguration; -import android.app.WindowConfiguration.WindowingMode; import android.content.Context; import android.content.Intent; import android.graphics.Rect; @@ -112,16 +111,13 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { primaryActivity, primaryRectBounds, null); // Create new empty task fragment - final int taskId = primaryContainer.getTaskId(); final TaskFragmentContainer secondaryContainer = mController.newContainer( - null /* activity */, primaryActivity, taskId); + null /* activity */, primaryActivity, primaryContainer.getTaskId()); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, isLtr(primaryActivity, rule)); - final int windowingMode = mController.getTaskContainer(taskId) - .getWindowingModeForSplitTaskFragment(secondaryRectBounds); createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), primaryActivity.getActivityToken(), secondaryRectBounds, - windowingMode); + WINDOWING_MODE_MULTI_WINDOW); secondaryContainer.setLastRequestedBounds(secondaryRectBounds); // Set adjacent to each other so that the containers below will be invisible. @@ -177,7 +173,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final WindowContainerTransaction wct = new WindowContainerTransaction(); createTaskFragment(wct, newContainer.getTaskFragmentToken(), - launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED); + launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_MULTI_WINDOW); applyTransaction(wct); return newContainer; @@ -193,17 +189,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) { TaskFragmentContainer container = mController.getContainerWithActivity( activity.getActivityToken()); - final int taskId = container != null ? container.getTaskId() : activity.getTaskId(); - final int windowingMode = mController.getTaskContainer(taskId) - .getWindowingModeForSplitTaskFragment(bounds); if (container == null || container == containerToAvoid) { - container = mController.newContainer(activity, taskId); + container = mController.newContainer(activity, activity.getTaskId()); + final TaskFragmentCreationParams fragmentOptions = createFragmentOptions( container.getTaskFragmentToken(), activity.getActivityToken(), bounds, - windowingMode); + WINDOWING_MODE_MULTI_WINDOW); wct.createTaskFragment(fragmentOptions); wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(), @@ -212,7 +206,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { container.setLastRequestedBounds(bounds); } else { resizeTaskFragmentIfRegistered(wct, container, bounds); - updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode); } return container; @@ -244,17 +237,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { launchingActivity.getTaskId()); } - final int taskId = primaryContainer.getTaskId(); TaskFragmentContainer secondaryContainer = mController.newContainer(null /* activity */, - launchingActivity, taskId); - final int windowingMode = mController.getTaskContainer(taskId) - .getWindowingModeForSplitTaskFragment(primaryRectBounds); + launchingActivity, primaryContainer.getTaskId()); final WindowContainerTransaction wct = new WindowContainerTransaction(); mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, rule); startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds, launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds, - activityIntent, activityOptions, rule, windowingMode); + activityIntent, activityOptions, rule); if (isPlaceholder) { // When placeholder is launched in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); @@ -302,12 +292,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // When placeholder is shown in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); } - final TaskContainer taskContainer = mController.getTaskContainer( - updatedContainer.getTaskId()); - final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment( - primaryRectBounds); - updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode); - updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode); } private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, @@ -339,15 +323,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds); } - private void updateTaskFragmentWindowingModeIfRegistered( - @NonNull WindowContainerTransaction wct, - @NonNull TaskFragmentContainer container, - @WindowingMode int windowingMode) { - if (container.getInfo() != null) { - wct.setWindowingMode(container.getInfo().getToken(), windowingMode); - } - } - @Override void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @Nullable Rect bounds) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index 3c0762d81494..be793018d969 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -16,14 +16,9 @@ package androidx.window.extensions.embedding; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; - import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.WindowConfiguration; -import android.app.WindowConfiguration.WindowingMode; +import android.content.res.Configuration; import android.graphics.Rect; import android.os.IBinder; import android.util.ArraySet; @@ -42,9 +37,9 @@ class TaskContainer { /** Available window bounds of this Task. */ private final Rect mTaskBounds = new Rect(); - /** Windowing mode of this Task. */ - @WindowingMode - private int mWindowingMode = WINDOWING_MODE_UNDEFINED; + /** Configuration of the Task. */ + @Nullable + private Configuration mConfiguration; /** Active TaskFragments in this Task. */ final List<TaskFragmentContainer> mContainers = new ArrayList<>(); @@ -86,42 +81,13 @@ class TaskContainer { return !mTaskBounds.isEmpty(); } - void setWindowingMode(int windowingMode) { - mWindowingMode = windowingMode; - } - - /** Whether the Task windowing mode has been initialized. */ - boolean isWindowingModeInitialized() { - return mWindowingMode != WINDOWING_MODE_UNDEFINED; - } - - /** - * Returns the windowing mode for the TaskFragments below this Task, which should be split with - * other TaskFragments. - * - * @param taskFragmentBounds Requested bounds for the TaskFragment. It will be empty when - * the pair of TaskFragments are stacked due to the limited space. - */ - @WindowingMode - int getWindowingModeForSplitTaskFragment(@Nullable Rect taskFragmentBounds) { - // Only set to multi-windowing mode if the pair are showing side-by-side. Otherwise, it - // will be set to UNDEFINED which will then inherit the Task windowing mode. - if (taskFragmentBounds == null || taskFragmentBounds.isEmpty()) { - return WINDOWING_MODE_UNDEFINED; - } - // We use WINDOWING_MODE_MULTI_WINDOW when the Task is fullscreen. - // However, when the Task is in other multi windowing mode, such as Freeform, we need to - // have the activity windowing mode to match the Task, otherwise things like - // DecorCaptionView won't work correctly. As a result, have the TaskFragment to be in the - // Task windowing mode if the Task is in multi window. - // TODO we won't need this anymore after we migrate Freeform caption to WM Shell. - return WindowConfiguration.inMultiWindowMode(mWindowingMode) - ? mWindowingMode - : WINDOWING_MODE_MULTI_WINDOW; + @Nullable + Configuration getConfiguration() { + return mConfiguration; } - boolean isInPictureInPicture() { - return mWindowingMode == WINDOWING_MODE_PINNED; + void setConfiguration(@Nullable Configuration configuration) { + mConfiguration = configuration; } /** Whether there is any {@link TaskFragmentContainer} below this Task. */ diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index 1f12c4484159..b06ce4c19d5c 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -16,23 +16,15 @@ package androidx.window.extensions.embedding; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; - import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import android.content.res.Configuration; -import android.graphics.Point; import android.platform.test.annotations.Presubmit; -import android.window.TaskFragmentInfo; -import android.window.WindowContainerToken; -import android.window.WindowContainerTransaction; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -43,8 +35,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.ArrayList; - /** * Test class for {@link JetpackTaskFragmentOrganizer}. * @@ -58,8 +48,6 @@ public class JetpackTaskFragmentOrganizerTest { private static final int TASK_ID = 10; @Mock - private WindowContainerTransaction mTransaction; - @Mock private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback; private JetpackTaskFragmentOrganizer mOrganizer; @@ -103,24 +91,4 @@ public class JetpackTaskFragmentOrganizerTest { verify(mOrganizer).unregisterRemoteAnimations(TASK_ID); } - - @Test - public void testExpandTaskFragment() { - final TaskFragmentContainer container = new TaskFragmentContainer(null, TASK_ID); - final TaskFragmentInfo info = createMockInfo(container); - mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info); - container.setInfo(info); - - mOrganizer.expandTaskFragment(mTransaction, container.getTaskFragmentToken()); - - verify(mTransaction).setWindowingMode(container.getInfo().getToken(), - WINDOWING_MODE_UNDEFINED); - } - - private TaskFragmentInfo createMockInfo(TaskFragmentContainer container) { - return new TaskFragmentInfo(container.getTaskFragmentToken(), - mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */, - false /* isVisible */, new ArrayList<>(), new Point(), - false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */); - } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index c7feb7e59de3..9fb08dffbab8 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -16,13 +16,6 @@ package androidx.window.extensions.embedding; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; - -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -71,56 +64,6 @@ public class TaskContainerTest { } @Test - public void testIsWindowingModeInitialized() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); - - assertFalse(taskContainer.isWindowingModeInitialized()); - - taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - - assertTrue(taskContainer.isWindowingModeInitialized()); - } - - @Test - public void testGetWindowingModeForSplitTaskFragment() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); - final Rect splitBounds = new Rect(0, 0, 500, 1000); - - assertEquals(WINDOWING_MODE_MULTI_WINDOW, - taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); - - taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - - assertEquals(WINDOWING_MODE_MULTI_WINDOW, - taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); - - taskContainer.setWindowingMode(WINDOWING_MODE_FREEFORM); - - assertEquals(WINDOWING_MODE_FREEFORM, - taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); - - // Empty bounds means the split pair are stacked, so it should be UNDEFINED which will then - // inherit the Task windowing mode - assertEquals(WINDOWING_MODE_UNDEFINED, - taskContainer.getWindowingModeForSplitTaskFragment(new Rect())); - } - - @Test - public void testIsInPictureInPicture() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); - - assertFalse(taskContainer.isInPictureInPicture()); - - taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - - assertFalse(taskContainer.isInPictureInPicture()); - - taskContainer.setWindowingMode(WINDOWING_MODE_PINNED); - - assertTrue(taskContainer.isInPictureInPicture()); - } - - @Test public void testIsEmpty() { final TaskContainer taskContainer = new TaskContainer(TASK_ID); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index a2b35fc9211a..a089585a5a00 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -454,8 +454,11 @@ public class BubbleExpandedView extends LinearLayout { p.beginRecording(mOverflowView.getWidth(), mOverflowView.getHeight())); p.endRecording(); Bitmap snapshot = Bitmap.createBitmap(p); - return new SurfaceControl.ScreenshotHardwareBuffer(snapshot.getHardwareBuffer(), - snapshot.getColorSpace(), false /* containsSecureLayers */); + return new SurfaceControl.ScreenshotHardwareBuffer( + snapshot.getHardwareBuffer(), + snapshot.getColorSpace(), + false /* containsSecureLayers */, + false /* containsHdrLayers */); } if (mTaskView == null || mTaskView.getSurfaceControl() == null) { return null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 9d6e34ddd9d7..91f9d2522397 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -1786,15 +1786,18 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onNoLongerSupportMultiWindow() { if (mMainStage.isActive()) { + final boolean isMainStage = mMainStageListener == this; if (!ENABLE_SHELL_TRANSITIONS) { - StageCoordinator.this.exitSplitScreen(null /* childrenToTop */, + StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); + return; } + final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); + prepareExitSplitScreen(stageType, wct); mSplitTransitions.startDismissTransition(null /* transition */, wct, - StageCoordinator.this, STAGE_TYPE_UNDEFINED, + StageCoordinator.this, stageType, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt index b7c80df03ce2..c9cab39b7d8b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt @@ -16,8 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.platform.test.annotations.Presubmit -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -31,6 +29,7 @@ import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSuppo import org.junit.After import org.junit.Before import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -76,23 +75,23 @@ class AppPairsTestCannotPairNonResizeableApps( resetMultiWindowConfig(instrumentation) } - @FlakyTest + @Ignore @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest(bugId = 206753786) + @Ignore @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - @Presubmit + @Ignore @Test override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - @Presubmit + @Ignore @Test fun appPairsDividerIsInvisibleAtEnd() = testSpec.appPairsDividerIsInvisibleAtEnd() - @Presubmit + @Ignore @Test fun onlyResizeableAppWindowVisible() { val nonResizeableApp = nonResizeableApp diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt index 1ac664eb1f62..60c32c99d1ff 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt @@ -16,8 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.platform.test.annotations.Presubmit -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -29,6 +27,7 @@ import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.AppPairsHelper import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -57,23 +56,23 @@ class AppPairsTestPairPrimaryAndSecondaryApps( } } - @Presubmit + @Ignore @Test override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - @FlakyTest + @Ignore @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest(bugId = 206753786) + @Ignore @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - @Presubmit + @Ignore @Test fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() - @Presubmit + @Ignore @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { @@ -82,7 +81,7 @@ class AppPairsTestPairPrimaryAndSecondaryApps( } } - @FlakyTest + @Ignore @Test fun appsEndingBounds() { testSpec.assertLayersEnd { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt index 57bcbc093a62..24869a802167 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt @@ -16,9 +16,7 @@ package com.android.wm.shell.flicker.apppairs -import android.platform.test.annotations.Presubmit import android.view.Display -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -33,6 +31,7 @@ import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSuppo import org.junit.After import org.junit.Before import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -89,23 +88,23 @@ class AppPairsTestSupportPairNonResizeableApps( resetMultiWindowConfig(instrumentation) } - @Presubmit + @Ignore @Test override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - @FlakyTest + @Ignore @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest(bugId = 206753786) + @Ignore @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - @Presubmit + @Ignore @Test fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() - @Presubmit + @Ignore @Test fun bothAppWindowVisible() { val nonResizeableApp = nonResizeableApp diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt index 12910dd74271..007415d19860 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt @@ -17,8 +17,6 @@ package com.android.wm.shell.flicker.apppairs import android.os.SystemClock -import android.platform.test.annotations.Presubmit -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -30,6 +28,7 @@ import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd import com.android.wm.shell.flicker.helpers.AppPairsHelper import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -65,19 +64,19 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( } } - @FlakyTest + @Ignore @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest(bugId = 206753786) + @Ignore @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - @Presubmit + @Ignore @Test fun appPairsDividerIsInvisibleAtEnd() = testSpec.appPairsDividerIsInvisibleAtEnd() - @Presubmit + @Ignore @Test fun bothAppWindowsInvisible() { testSpec.assertWmEnd { @@ -86,7 +85,7 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( } } - @FlakyTest + @Ignore @Test fun appsStartingBounds() { testSpec.assertLayersStart { @@ -98,7 +97,7 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( } } - @FlakyTest + @Ignore @Test fun appsEndingBounds() { testSpec.assertLayersEnd { @@ -107,7 +106,7 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( } } - @Presubmit + @Ignore @Test override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt index 863c3aff63a2..3e17948b4a84 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt @@ -18,9 +18,7 @@ package com.android.wm.shell.flicker.apppairs import android.app.Instrumentation import android.content.Context -import android.platform.test.annotations.Presubmit import android.system.helpers.ActivityHelper -import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter @@ -42,6 +40,7 @@ import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.testapp.Components import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) { @@ -145,35 +144,35 @@ abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) append("$primaryApp $secondaryApp") } - @FlakyTest(bugId = 186510496) + @Ignore @Test open fun navBarLayerIsVisible() { testSpec.navBarLayerIsVisible() } - @Presubmit + @Ignore @Test open fun statusBarLayerIsVisible() { testSpec.statusBarLayerIsVisible() } - @Presubmit + @Ignore @Test open fun navBarWindowIsVisible() { testSpec.navBarWindowIsVisible() } - @Presubmit + @Ignore @Test open fun statusBarWindowIsVisible() { testSpec.statusBarWindowIsVisible() } - @Presubmit + @Ignore @Test open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - @Presubmit + @Ignore @Test open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt index f2f4877a44c4..b0c3ba20d948 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt @@ -16,9 +16,7 @@ package com.android.wm.shell.flicker.apppairs -import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -32,6 +30,7 @@ import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -60,15 +59,15 @@ class RotateTwoLaunchedAppsInAppPairsMode( } } - @Presubmit + @Ignore @Test override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - @Presubmit + @Ignore @Test override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() - @Presubmit + @Ignore @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { @@ -77,23 +76,23 @@ class RotateTwoLaunchedAppsInAppPairsMode( } } - @Presubmit + @Ignore @Test fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() - @Presubmit + @Ignore @Test fun appPairsPrimaryBoundsIsVisibleAtEnd() = testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation, primaryApp.component) - @Presubmit + @Ignore @Test fun appPairsSecondaryBoundsIsVisibleAtEnd() = testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation, secondaryApp.component) - @FlakyTest(bugId = 206753786) + @Ignore @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt index 2a173d16004f..ae56c7732a4d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt @@ -16,9 +16,7 @@ package com.android.wm.shell.flicker.apppairs -import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -32,6 +30,7 @@ import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -60,31 +59,31 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode( } } - @Presubmit + @Ignore @Test fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() - @Presubmit + @Ignore @Test override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() - @Presubmit + @Ignore @Test override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - @Presubmit + @Ignore @Test override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() - @Presubmit + @Ignore @Test override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() - @FlakyTest(bugId = 206753786) + @Ignore @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - @Presubmit + @Ignore @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { @@ -93,13 +92,13 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode( } } - @Presubmit + @Ignore @Test fun appPairsPrimaryBoundsIsVisibleAtEnd() = testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation, primaryApp.component) - @Presubmit + @Ignore @Test fun appPairsSecondaryBoundsIsVisibleAtEnd() = testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt index 670fbd810907..b1f1c9e539df 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.apppairs import android.view.Surface -import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.setRotation @@ -26,6 +25,7 @@ import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTrans import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.Assume.assumeFalse import org.junit.Before +import org.junit.Ignore import org.junit.Test abstract class RotateTwoLaunchedAppsTransition( @@ -62,13 +62,13 @@ abstract class RotateTwoLaunchedAppsTransition( super.setup() } - @FlakyTest + @Ignore @Test override fun navBarLayerIsVisible() { super.navBarLayerIsVisible() } - @FlakyTest + @Ignore @Test override fun navBarLayerRotatesAndScales() { super.navBarLayerRotatesAndScales() diff --git a/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml b/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml index 7c3f5a566bdb..a596a9a59ee8 100644 --- a/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml +++ b/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml @@ -28,7 +28,8 @@ style="@style/SettingsLibActionButton" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="1"/> + android:layout_weight="1" + android:hyphenationFrequency="normalFast"/> <View android:id="@+id/divider1" @@ -43,7 +44,8 @@ style="@style/SettingsLibActionButton" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="1"/> + android:layout_weight="1" + android:hyphenationFrequency="normalFast"/> <View android:id="@+id/divider2" @@ -58,7 +60,8 @@ style="@style/SettingsLibActionButton" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="1"/> + android:layout_weight="1" + android:hyphenationFrequency="normalFast"/> <View android:id="@+id/divider3" @@ -73,5 +76,6 @@ style="@style/SettingsLibActionButton" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="1"/> + android:layout_weight="1" + android:hyphenationFrequency="normalFast"/> </LinearLayout> diff --git a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java b/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java index b038d59e9fe4..5693c2f22d1e 100644 --- a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java +++ b/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java @@ -42,15 +42,13 @@ public class WorkPolicyUtils { private static final int USER_NULL = -10000; public WorkPolicyUtils( - Context applicationContext, - PackageManager mPm, - UserManager mUm, - DevicePolicyManager mDpm + Context context ) { - mContext = applicationContext; - mPackageManager = mPm; - mUserManager = mUm; - mDevicePolicyManager = mDpm; + mContext = context; + mPackageManager = context.getPackageManager(); + mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + mDevicePolicyManager = + (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index 440a54435fc3..affcf585904a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -332,6 +332,9 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { */ @Override public int compareTo(MediaDevice another) { + if (another == null) { + return -1; + } // Check Bluetooth device is have same connection state if (isConnected() ^ another.isConnected()) { if (isConnected()) { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java index 552fa11a42b7..3514932d4e8d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java @@ -48,6 +48,12 @@ public class MediaOutputConstants { "com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG"; /** + * An intent action to launch media output broadcast dialog. + */ + public static final String ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG = + "com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG"; + + /** * Settings package name. */ public static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index f949f99673d9..3029781f3e99 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -117,6 +117,9 @@ public class SecureSettings { Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD, Settings.Secure.FACE_UNLOCK_APP_ENABLED, Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, + Settings.Secure.ACTIVE_UNLOCK_ON_WAKE, + Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, + Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, Settings.Secure.VR_DISPLAY_MODE, Settings.Secure.NOTIFICATION_BADGING, Settings.Secure.NOTIFICATION_DISMISS_RTL, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 2bdf81912709..a4da49713f87 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -173,6 +173,9 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_WAKE, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ASSIST_GESTURE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index f1b23d5733af..4b7d0d2a3c74 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -597,6 +597,7 @@ public class SettingsBackupTest { Settings.Global.CLOCKWORK_HOME_READY, Settings.Global.WATCHDOG_TIMEOUT_MILLIS, Settings.Global.MANAGED_PROVISIONING_DEFER_PROVISIONING_TO_ROLE_HOLDER, + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, Settings.Global.Wearable.BATTERY_SAVER_MODE, Settings.Global.Wearable.COMBINED_LOCATION_ENABLED, Settings.Global.Wearable.HAS_PAY_TOKENS, diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 6887d037c6f4..290ce345694e 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -931,6 +931,7 @@ android:exported="true"> <intent-filter> <action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" /> + <action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG" /> <action android:name="com.android.systemui.action.DISMISS_MEDIA_OUTPUT_DIALOG" /> </intent-filter> </receiver> diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt index 46dad02ddb45..dd45b6f39bdd 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -23,6 +23,7 @@ import com.android.internal.graphics.ColorUtils import com.android.internal.graphics.cam.Cam import com.android.internal.graphics.cam.CamUtils.lstarFromInt import kotlin.math.absoluteValue +import kotlin.math.max import kotlin.math.roundToInt const val TAG = "ColorScheme" @@ -78,36 +79,32 @@ internal class HueSubtract(val amountDegrees: Double) : Hue { } internal class HueVibrantSecondary() : Hue { - val hueToRotations = listOf(Pair(24, 15), Pair(53, 15), Pair(91, 15), Pair(123, 15), - Pair(141, 15), Pair(172, 15), Pair(198, 15), Pair(234, 18), Pair(272, 18), - Pair(302, 18), Pair(329, 30), Pair(354, 15)) + val hueToRotations = listOf(Pair(0, 18), Pair(41, 15), Pair(61, 10), Pair(101, 12), + Pair(131, 15), Pair(181, 18), Pair(251, 15), Pair(301, 12)) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) } } internal class HueVibrantTertiary() : Hue { - val hueToRotations = listOf(Pair(24, 30), Pair(53, 30), Pair(91, 15), Pair(123, 30), - Pair(141, 27), Pair(172, 27), Pair(198, 30), Pair(234, 35), Pair(272, 30), - Pair(302, 30), Pair(329, 60), Pair(354, 30)) + val hueToRotations = listOf(Pair(0, 35), Pair(41, 30), Pair(61, 20), Pair(101, 25), + Pair(131, 30), Pair(181, 35), Pair(251, 30), Pair(301, 25)) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) } } internal class HueExpressiveSecondary() : Hue { - val hueToRotations = listOf(Pair(24, 95), Pair(53, 45), Pair(91, 45), Pair(123, 20), - Pair(141, 45), Pair(172, 45), Pair(198, 15), Pair(234, 15), - Pair(272, 45), Pair(302, 45), Pair(329, 45), Pair(354, 45)) + val hueToRotations = listOf(Pair(0, 45), Pair(21, 95), Pair(51, 45), Pair(121, 20), + Pair(141, 45), Pair(191, 90), Pair(271, 45), Pair(321, 45)) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) } } internal class HueExpressiveTertiary() : Hue { - val hueToRotations = listOf(Pair(24, 20), Pair(53, 20), Pair(91, 20), Pair(123, 45), - Pair(141, 20), Pair(172, 20), Pair(198, 90), Pair(234, 90), Pair(272, 20), - Pair(302, 20), Pair(329, 120), Pair(354, 120)) + val hueToRotations = listOf(Pair(0, 120), Pair(21, 120), Pair(51, 20), Pair(121, 45), + Pair(141, 20), Pair(191, 15), Pair(271, 20), Pair(321, 120)) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) } @@ -140,18 +137,15 @@ internal interface Chroma { } } -internal class ChromaConstant(val chroma: Double) : Chroma { +internal class ChromaMinimum(val chroma: Double) : Chroma { override fun get(sourceColor: Cam): Double { - return chroma + return max(sourceColor.chroma.toDouble(), chroma) } } -internal class ChromaExpressiveNeutral() : Chroma { - val hueToChromas = listOf(Pair(24, 8), Pair(53, 8), Pair(91, 8), Pair(123, 8), - Pair(141, 6), Pair(172, 6), Pair(198, 8), Pair(234, 8), Pair(272, 8), - Pair(302, 8), Pair(329, 8), Pair(354, 8)) +internal class ChromaConstant(val chroma: Double) : Chroma { override fun get(sourceColor: Cam): Double { - return getSpecifiedChroma(sourceColor.hue, hueToChromas) + return chroma } } @@ -187,17 +181,17 @@ enum class Style(internal val coreSpec: CoreSpec) { n2 = TonalSpec(HueSource(), ChromaConstant(8.0)) )), VIBRANT(CoreSpec( - a1 = TonalSpec(HueSource(), ChromaConstant(48.0)), + a1 = TonalSpec(HueSource(), ChromaMinimum(48.0)), a2 = TonalSpec(HueVibrantSecondary(), ChromaConstant(24.0)), a3 = TonalSpec(HueVibrantTertiary(), ChromaConstant(32.0)), - n1 = TonalSpec(HueSource(), ChromaConstant(6.0)), + n1 = TonalSpec(HueSource(), ChromaConstant(10.0)), n2 = TonalSpec(HueSource(), ChromaConstant(12.0)) )), EXPRESSIVE(CoreSpec( a1 = TonalSpec(HueAdd(240.0), ChromaConstant(40.0)), a2 = TonalSpec(HueExpressiveSecondary(), ChromaConstant(24.0)), - a3 = TonalSpec(HueExpressiveTertiary(), ChromaConstant(40.0)), - n1 = TonalSpec(HueAdd(15.0), ChromaExpressiveNeutral()), + a3 = TonalSpec(HueExpressiveTertiary(), ChromaConstant(32.0)), + n1 = TonalSpec(HueAdd(15.0), ChromaConstant(8.0)), n2 = TonalSpec(HueAdd(15.0), ChromaConstant(12.0)) )), RAINBOW(CoreSpec( @@ -231,8 +225,13 @@ class ColorScheme( constructor(@ColorInt seed: Int, darkTheme: Boolean): this(seed, darkTheme, Style.TONAL_SPOT) - constructor(wallpaperColors: WallpaperColors, darkTheme: Boolean): - this(getSeedColor(wallpaperColors), darkTheme) + @JvmOverloads + constructor( + wallpaperColors: WallpaperColors, + darkTheme: Boolean, + style: Style = Style.TONAL_SPOT + ): + this(getSeedColor(wallpaperColors), darkTheme, style) val allAccentColors: List<Int> get() { diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml index 9ffafbc8cc09..6757acf7014a 100644 --- a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml +++ b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml @@ -55,6 +55,15 @@ android:textColor="?android:attr/textColorSecondary"/> <ImageView + android:id="@+id/fgs_new" + android:layout_width="12dp" + android:layout_height="12dp" + android:scaleType="fitCenter" + android:src="@drawable/fgs_dot" + android:contentDescription="@string/fgs_dot_content_description" + /> + + <ImageView android:id="@+id/footer_icon" android:layout_width="@dimen/qs_footer_icon_size" android:layout_height="@dimen/qs_footer_icon_size" @@ -82,7 +91,7 @@ android:textColor="?android:attr/textColorPrimary" android:textSize="18sp"/> <ImageView - android:id="@+id/fgs_new" + android:id="@+id/fgs_collapsed_new" android:layout_width="12dp" android:layout_height="12dp" android:scaleType="fitCenter" diff --git a/packages/SystemUI/res/layout/media_output_broadcast_update_dialog.xml b/packages/SystemUI/res/layout/media_output_broadcast_update_dialog.xml index 8b7a019b791b..89dcbcc5929f 100644 --- a/packages/SystemUI/res/layout/media_output_broadcast_update_dialog.xml +++ b/packages/SystemUI/res/layout/media_output_broadcast_update_dialog.xml @@ -19,11 +19,19 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="?android:attr/dialogPreferredPadding" - android:paddingRight="?android:attr/dialogPreferredPadding"> + android:paddingRight="?android:attr/dialogPreferredPadding" + android:orientation="vertical"> <EditText android:id="@+id/broadcast_edit_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="48dp" android:textAlignment="viewStart"/> + <TextView + android:id="@+id/broadcast_error_message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="6dp" + style="@style/TextAppearance.ErrorText" + android:visibility="invisible"/> </LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml index b85ea598dbb4..0e9700fe6b1e 100644 --- a/packages/SystemUI/res/layout/media_output_list_item.xml +++ b/packages/SystemUI/res/layout/media_output_list_item.xml @@ -104,7 +104,7 @@ android:layout_height="24dp" android:layout_marginEnd="16dp" android:indeterminate="true" - android:layout_gravity="right|center" + android:layout_gravity="end|center" android:indeterminateOnly="true" android:visibility="gone"/> @@ -114,7 +114,7 @@ android:layout_height="24dp" android:layout_marginEnd="16dp" android:indeterminate="true" - android:layout_gravity="right|center" + android:layout_gravity="end|center" android:indeterminateOnly="true" android:importantForAccessibility="no" android:visibility="gone"/> @@ -125,14 +125,14 @@ android:orientation="vertical" android:layout_width="48dp" android:layout_height="64dp" - android:layout_gravity="right|center" + android:layout_gravity="end|center" android:gravity="center_vertical"> <CheckBox android:id="@+id/check_box" android:layout_width="24dp" android:layout_height="24dp" android:layout_marginEnd="16dp" - android:layout_gravity="right" + android:layout_gravity="end" android:button="@drawable/ic_circle_check_box" android:visibility="gone" /> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4a8fd1b00dde..4370432d85a9 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -388,6 +388,8 @@ <dimen name="split_shade_notifications_scrim_margin_bottom">0dp</dimen> + <dimen name="shelf_and_lock_icon_overlap">5dp</dimen> + <dimen name="notification_panel_margin_horizontal">0dp</dimen> <dimen name="brightness_mirror_height">48dp</dimen> @@ -1179,6 +1181,10 @@ <!-- Maximum over scroll amount for the shade when transition to the full shade. --> <dimen name="lockscreen_shade_max_over_scroll_amount">24dp</dimen> + <!-- Maximum over scroll amount for the shade when transition to the full shade. + Only used for split-shade. --> + <dimen name="shade_max_over_scroll_amount">@dimen/lockscreen_shade_max_over_scroll_amount</dimen> + <!-- Maximum overshoot for the pulse expansion --> <dimen name="pulse_expansion_max_top_overshoot">32dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e1fa5752e2d3..6cc61b4706f6 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2293,6 +2293,11 @@ <string name="media_output_broadcast_starting">Starting…</string> <!-- The button text when Broadcast start failed [CHAR LIMIT=60] --> <string name="media_output_broadcast_start_failed">Can\u2019t broadcast</string> + <!-- The error message when Broadcast name/code update failed [CHAR LIMIT=60] --> + <string name="media_output_broadcast_update_error">Can\u2019t save. Try again.</string> + <!-- The error message when Broadcast name/code update failed and can't change again[CHAR LIMIT=60] --> + <string name="media_output_broadcast_last_update_error">Can\u2019t save.</string> + <!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]--> <string name="build_number_clip_data_label">Build number</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl index b2295b94127b..5a30901f1a8b 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl @@ -16,17 +16,20 @@ package com.android.systemui.shared.system.smartspace; +import android.graphics.Rect; import com.android.systemui.shared.system.smartspace.SmartspaceState; // Methods for System UI to interface with Launcher to perform the unlock animation. interface ILauncherUnlockAnimationController { // Prepares Launcher for the unlock animation by setting scale/alpha/etc. to their starting // values. - void prepareForUnlock(boolean willAnimateSmartspace, int selectedPage); + void prepareForUnlock(boolean animateSmartspace, in Rect lockscreenSmartspaceBounds, + int selectedPage); // Set the unlock percentage. This is used when System UI is controlling each frame of the - // unlock animation, such as during a swipe to unlock touch gesture. - oneway void setUnlockAmount(float amount); + // unlock animation, such as during a swipe to unlock touch gesture. Will not apply this change + // if the unlock amount is animating unless forceIfAnimating is true. + oneway void setUnlockAmount(float amount, boolean forceIfAnimating); // Play a full unlock animation from 0f to 1f. This is used when System UI is unlocking from a // single action, such as biometric auth, and doesn't need to control individual frames. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 04c9a45af065..ea14b64b8b18 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -58,9 +58,7 @@ import com.android.systemui.util.ViewController; import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; -import java.util.HashSet; import java.util.Locale; -import java.util.Set; import java.util.TimeZone; import java.util.concurrent.Executor; @@ -134,14 +132,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mKeyguardUnlockAnimationListener = new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() { @Override - public void onSmartspaceSharedElementTransitionStarted() { - // The smartspace needs to be able to translate out of bounds in order to - // end up where the launcher's smartspace is, while its container is being - // swiped off the top of the screen. - setClipChildrenForUnlock(false); - } - - @Override public void onUnlockAnimationFinished() { // For performance reasons, reset this once the unlock animation ends. setClipChildrenForUnlock(true); @@ -390,41 +380,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS if (mStatusArea != null) { PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X, x, props, animate); - - // If we're unlocking with the SmartSpace shared element transition, let the controller - // know that it should re-position our SmartSpace. - if (mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) { - mKeyguardUnlockAnimationController.updateLockscreenSmartSpacePosition(); - } - } - } - - /** Sets an alpha value on every child view except for the smartspace. */ - public void setChildrenAlphaExcludingSmartspace(float alpha) { - final Set<View> excludedViews = new HashSet<>(); - - if (mSmartspaceView != null) { - excludedViews.add(mStatusArea); - } - - // Don't change the alpha of the invisible clock. - if (mCurrentClockSize == LARGE) { - excludedViews.add(mClockFrame); - } else { - excludedViews.add(mLargeClockFrame); - } - - setChildrenAlphaExcluding(alpha, excludedViews); - } - - /** Sets an alpha value on every child view except for the views in the provided set. */ - public void setChildrenAlphaExcluding(float alpha, Set<View> excludedViews) { - for (int i = 0; i < mView.getChildCount(); i++) { - final View child = mView.getChildAt(i); - - if (!excludedViews.contains(child)) { - child.setAlpha(alpha); - } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 1ede76fb1fa4..02776a295359 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -16,7 +16,6 @@ package com.android.keyguard; import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.Display.DEFAULT_DISPLAY_GROUP; import android.app.Presentation; import android.content.Context; @@ -119,10 +118,9 @@ public class KeyguardDisplayManager { if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on a private display"); return false; } - if (mTmpDisplayInfo.displayGroupId != DEFAULT_DISPLAY_GROUP) { + if ((mTmpDisplayInfo.flags & Display.FLAG_ALWAYS_UNLOCKED) != 0) { if (DEBUG) { - Log.i(TAG, - "Do not show KeyguardPresentation on a non-default group display"); + Log.i(TAG, "Do not show KeyguardPresentation on an unlocked display"); } return false; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java index 239b478949d2..804d14681812 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java @@ -68,12 +68,6 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView> private final KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @Override - public void onUserSwitchComplete(int userId) { - mKeyguardSecurityContainerController.showPrimarySecurityScreen( - false /* turning off */); - } - - @Override public void onTrustGrantedWithFlags(int flags, int userId) { if (userId != KeyguardUpdateMonitor.getCurrentUser()) return; boolean bouncerVisible = mView.isVisibleToUser(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 75425e1e6ca3..6a68c70c6acb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -28,6 +28,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -168,6 +169,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory; private final FalsingCollector mFalsingCollector; private final DevicePostureController mDevicePostureController; + private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Inject public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -178,7 +180,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> @Main Resources resources, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController.Factory emergencyButtonControllerFactory, - DevicePostureController devicePostureController) { + DevicePostureController devicePostureController, + StatusBarKeyguardViewManager statusBarKeyguardViewManager) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; @@ -191,6 +194,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mEmergencyButtonControllerFactory = emergencyButtonControllerFactory; mFalsingCollector = falsingCollector; mDevicePostureController = devicePostureController; + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; } /** Create a new {@link KeyguardInputViewController}. */ @@ -211,7 +215,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mInputMethodManager, emergencyButtonController, mMainExecutor, mResources, - mFalsingCollector); + mFalsingCollector, mStatusBarKeyguardViewManager); } else if (keyguardInputView instanceof KeyguardPINView) { return new KeyguardPinViewController((KeyguardPINView) keyguardInputView, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 0529cdbcbb13..19035264db7f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -43,6 +43,7 @@ import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.List; @@ -55,6 +56,7 @@ public class KeyguardPasswordViewController private final KeyguardSecurityCallback mKeyguardSecurityCallback; private final InputMethodManager mInputMethodManager; private final DelayableExecutor mMainExecutor; + private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final boolean mShowImeAtScreenOn; private EditText mPasswordEntry; private ImageView mSwitchImeButton; @@ -116,13 +118,15 @@ public class KeyguardPasswordViewController EmergencyButtonController emergencyButtonController, @Main DelayableExecutor mainExecutor, @Main Resources resources, - FalsingCollector falsingCollector) { + FalsingCollector falsingCollector, + StatusBarKeyguardViewManager statusBarKeyguardViewManager) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, falsingCollector, emergencyButtonController); mKeyguardSecurityCallback = keyguardSecurityCallback; mInputMethodManager = inputMethodManager; mMainExecutor = mainExecutor; + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on); mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); mSwitchImeButton = mView.findViewById(R.id.switch_ime_button); @@ -205,6 +209,10 @@ public class KeyguardPasswordViewController } private void showInput() { + if (!mStatusBarKeyguardViewManager.isBouncerShowing()) { + return; + } + mView.post(() -> { if (mView.isShown()) { mPasswordEntry.requestFocus(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index ce4aad882df9..28a3dbbf6c83 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -97,6 +97,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED; private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid; + private UserSwitcherController.UserSwitchCallback mUserSwitchCallback = + () -> showPrimarySecurityScreen(false); @VisibleForTesting final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() { @@ -295,6 +297,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mView.setSwipeListener(mSwipeListener); mView.addMotionEventListener(mGlobalTouchListener); mConfigurationController.addCallback(mConfigurationListener); + mUserSwitcherController.addUserSwitchCallback(mUserSwitchCallback); } @Override @@ -302,6 +305,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); mConfigurationController.removeCallback(mConfigurationListener); mView.removeMotionEventListener(mGlobalTouchListener); + mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback); } /** */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index 853d7402a1f8..cb3172dabdb1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -47,7 +47,6 @@ public class KeyguardStatusView extends GridLayout { private float mDarkAmount = 0; private int mTextColor; - private float mChildrenAlphaExcludingSmartSpace = 1f; public KeyguardStatusView(Context context) { this(context, null, 0); @@ -95,23 +94,6 @@ public class KeyguardStatusView extends GridLayout { mClockView.setTextColor(blendedTextColor); } - public void setChildrenAlphaExcludingClockView(float alpha) { - setChildrenAlphaExcluding(alpha, Set.of(mClockView)); - } - - /** Sets an alpha value on every view except for the views in the provided set. */ - public void setChildrenAlphaExcluding(float alpha, Set<View> excludedViews) { - mChildrenAlphaExcludingSmartSpace = alpha; - - for (int i = 0; i < mStatusViewContainer.getChildCount(); i++) { - final View child = mStatusViewContainer.getChildAt(i); - - if (!excludedViews.contains(child)) { - child.setAlpha(alpha); - } - } - } - /** Sets a translationY value on every child view except for the media view. */ public void setChildrenTranslationYExcludingMediaView(float translationY) { setChildrenTranslationYExcluding(translationY, Set.of(mMediaHostContainer)); @@ -128,10 +110,6 @@ public class KeyguardStatusView extends GridLayout { } } - public float getChildrenAlphaExcludingSmartSpace() { - return mChildrenAlphaExcludingSmartSpace; - } - public void dump(PrintWriter pw, String[] args) { pw.println("KeyguardStatusView:"); pw.println(" mDarkAmount: " + mDarkAmount); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 14c9cb2022bc..083f2fe53d17 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -20,7 +20,6 @@ import android.graphics.Rect; import android.util.Slog; import com.android.keyguard.KeyguardClockSwitch.ClockSize; -import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.AnimationProperties; @@ -49,10 +48,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final KeyguardClockSwitchController mKeyguardClockSwitchController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final ConfigurationController mConfigurationController; - private final DozeParameters mDozeParameters; private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; - private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; - private final KeyguardStateController mKeyguardStateController; private final Rect mClipBounds = new Rect(); @Inject @@ -64,18 +60,14 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV KeyguardUpdateMonitor keyguardUpdateMonitor, ConfigurationController configurationController, DozeParameters dozeParameters, - KeyguardUnlockAnimationController keyguardUnlockAnimationController, ScreenOffAnimationController screenOffAnimationController) { super(keyguardStatusView); mKeyguardSliceViewController = keyguardSliceViewController; mKeyguardClockSwitchController = keyguardClockSwitchController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mConfigurationController = configurationController; - mDozeParameters = dozeParameters; - mKeyguardStateController = keyguardStateController; mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ true); - mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; } @Override @@ -87,14 +79,12 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV protected void onViewAttached() { mKeyguardUpdateMonitor.registerCallback(mInfoCallback); mConfigurationController.addCallback(mConfigurationListener); - mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); } @Override protected void onViewDetached() { mKeyguardUpdateMonitor.removeCallback(mInfoCallback); mConfigurationController.removeCallback(mConfigurationListener); - mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback); } /** @@ -148,24 +138,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV */ public void setAlpha(float alpha) { if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { - // If we're capable of performing the SmartSpace shared element transition, and we are - // going to (we're swiping to dismiss vs. bringing up the PIN screen), then fade out - // everything except for the SmartSpace. - if (mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) { - mView.setChildrenAlphaExcludingClockView(alpha); - mKeyguardClockSwitchController.setChildrenAlphaExcludingSmartspace(alpha); - } else if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { - // Otherwise, we can just set the alpha for the entire container. - mView.setAlpha(alpha); - - // If we previously unlocked with the shared element transition, some child views - // might still have alpha = 0f. Set them back to 1f since we're just using the - // parent container's alpha. - if (mView.getChildrenAlphaExcludingSmartSpace() < 1f) { - mView.setChildrenAlphaExcludingClockView(1f); - mKeyguardClockSwitchController.setChildrenAlphaExcludingSmartspace(1f); - } - } + mView.setAlpha(alpha); } } @@ -289,19 +262,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } }; - private KeyguardStateController.Callback mKeyguardStateControllerCallback = - new KeyguardStateController.Callback() { - @Override - public void onKeyguardShowingChanged() { - // If we explicitly re-show the keyguard, make sure that all the child views are - // visible. They might have been animating out as part of the SmartSpace shared - // element transition. - if (mKeyguardStateController.isShowing()) { - mView.setChildrenAlphaExcludingClockView(1f); - } - } - }; - /** * Rect that specifies how KSV should be clipped, on its parent's coordinates. */ diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 239730d18934..95cda8b9dfca 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -668,7 +668,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mVelocityTracker.recycle(); mVelocityTracker = null; } - mVibrator.cancel(); } private boolean inLockIconArea(MotionEvent event) { diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java index f925eaa0e40b..eb6705a2e979 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java @@ -15,6 +15,8 @@ */ package com.android.keyguard; +import static com.android.systemui.util.ColorUtilKt.getPrivateAttrColorIfUnset; + import android.animation.AnimatorSet; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; @@ -152,7 +154,7 @@ class NumPadAnimator { ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle); TypedArray a = ctw.obtainStyledAttributes(customAttrs); - mNormalColor = Utils.getPrivateAttrColorIfUnset(ctw, a, 0, 0, + mNormalColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0, com.android.internal.R.attr.colorSurface); mHighlightColor = a.getColor(1, 0); a.recycle(); diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt index ccb5b1146a1c..e51a63fbcd10 100644 --- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt +++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt @@ -228,14 +228,20 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { if (pendingRotationChange) { return } + val m = Matrix() + // Apply display ratio. + val physicalPixelDisplaySizeRatio = getPhysicalPixelDisplaySizeRatio() + m.postScale(physicalPixelDisplaySizeRatio, physicalPixelDisplaySizeRatio) + + // Apply rotation. val lw: Int = displayInfo.logicalWidth val lh: Int = displayInfo.logicalHeight val flipped = (displayInfo.rotation == Surface.ROTATION_90 || displayInfo.rotation == Surface.ROTATION_270) val dw = if (flipped) lh else lw val dh = if (flipped) lw else lh - val m = Matrix() transformPhysicalToLogicalCoordinates(displayInfo.rotation, dw, dh, m) + if (!protectionPathOrig.isEmpty) { // Reset the protection path so we don't aggregate rotations protectionPath.set(protectionPathOrig) @@ -244,6 +250,14 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { } } + @VisibleForTesting + open fun getPhysicalPixelDisplaySizeRatio(): Float { + displayInfo.displayCutout?.let { + return it.cutoutPathParserInfo.physicalPixelDisplaySizeRatio + } + return 1f + } + private fun displayModeChanged(oldMode: Display.Mode?, newMode: Display.Mode?): Boolean { if (oldMode == null) { return true @@ -265,17 +279,17 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { out: Matrix ) { when (rotation) { - Surface.ROTATION_0 -> out.reset() + Surface.ROTATION_0 -> return Surface.ROTATION_90 -> { - out.setRotate(270f) + out.postRotate(270f) out.postTranslate(0f, physicalWidth.toFloat()) } Surface.ROTATION_180 -> { - out.setRotate(180f) + out.postRotate(180f) out.postTranslate(physicalWidth.toFloat(), physicalHeight.toFloat()) } Surface.ROTATION_270 -> { - out.setRotate(90f) + out.postRotate(90f) out.postTranslate(physicalHeight.toFloat(), 0f) } else -> throw IllegalArgumentException("Unknown rotation: $rotation") diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java index 59c658fa43d2..49e378e4a76f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java @@ -22,6 +22,10 @@ import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.drawable.Drawable; +import android.os.Process; +import android.os.VibrationAttributes; +import android.os.VibrationEffect; +import android.os.Vibrator; import android.view.accessibility.AccessibilityManager; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; @@ -46,6 +50,17 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { private static final float STROKE_WIDTH_DP = 12f; private static final Interpolator DEACCEL = new DecelerateInterpolator(); + private static final VibrationEffect VIBRATE_EFFECT_ERROR = + VibrationEffect.createWaveform(new long[] {0, 5, 55, 60}, -1); + private static final VibrationAttributes FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY); + + private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); + + private static final VibrationEffect SUCCESS_VIBRATION_EFFECT = + VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + private final float mStrokeWidthPx; @ColorInt private final int mProgressColor; @ColorInt private final int mHelpColor; @@ -54,6 +69,9 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { @NonNull private final Interpolator mCheckmarkInterpolator; @NonNull private final Paint mBackgroundPaint; @NonNull private final Paint mFillPaint; + @NonNull private final Vibrator mVibrator; + @NonNull private final boolean mIsAccessibilityEnabled; + @NonNull private final Context mContext; private boolean mAfterFirstTouch; @@ -76,11 +94,12 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { @NonNull private final ValueAnimator.AnimatorUpdateListener mCheckmarkUpdateListener; public UdfpsEnrollProgressBarDrawable(@NonNull Context context) { + mContext = context; mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP); mProgressColor = context.getColor(R.color.udfps_enroll_progress); final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); - final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled(); - if (!isAccessbilityEnabled) { + mIsAccessibilityEnabled = am.isTouchExplorationEnabled(); + if (!mIsAccessibilityEnabled) { mHelpColor = context.getColor(R.color.udfps_enroll_progress_help); mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error); } else { @@ -106,6 +125,8 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { mFillPaint.setStyle(Paint.Style.STROKE); mFillPaint.setStrokeCap(Paint.Cap.ROUND); + mVibrator = mContext.getSystemService(Vibrator.class); + mProgressUpdateListener = animation -> { mProgress = (float) animation.getAnimatedValue(); invalidateSelf(); @@ -141,14 +162,41 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { } private void updateState(int remainingSteps, int totalSteps, boolean showingHelp) { - updateProgress(remainingSteps, totalSteps); + updateProgress(remainingSteps, totalSteps, showingHelp); updateFillColor(showingHelp); } - private void updateProgress(int remainingSteps, int totalSteps) { + private void updateProgress(int remainingSteps, int totalSteps, boolean showingHelp) { if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) { return; } + + if (mShowingHelp) { + if (mVibrator != null && mIsAccessibilityEnabled) { + mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(), + VIBRATE_EFFECT_ERROR, getClass().getSimpleName() + "::onEnrollmentHelp", + FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES); + } + } else { + // If the first touch is an error, remainingSteps will be -1 and the callback + // doesn't come from onEnrollmentHelp. If we are in the accessibility flow, + // we still would like to vibrate. + if (mVibrator != null) { + if (remainingSteps == -1 && mIsAccessibilityEnabled) { + mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(), + VIBRATE_EFFECT_ERROR, + getClass().getSimpleName() + "::onFirstTouchError", + FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES); + } else if (remainingSteps != -1 && !mIsAccessibilityEnabled) { + mVibrator.vibrate(Process.myUid(), + mContext.getOpPackageName(), + SUCCESS_VIBRATION_EFFECT, + getClass().getSimpleName() + "::OnEnrollmentProgress", + HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); + } + } + } + mRemainingSteps = remainingSteps; mTotalSteps = totalSteps; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index a8c286241141..b21a886b037d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -23,7 +23,7 @@ import android.content.Context import android.graphics.Matrix import android.graphics.Rect import android.os.Handler -import android.provider.Settings +import android.os.RemoteException import android.util.Log import android.view.RemoteAnimationTarget import android.view.SyncRtSurfaceTransactionApplier @@ -47,7 +47,6 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Lazy import javax.inject.Inject -import kotlin.math.min const val TAG = "KeyguardUnlock" @@ -77,7 +76,7 @@ const val SURFACE_BEHIND_SCALE_PIVOT_Y = 0.66f * The dismiss amount is the inverse of the notification panel expansion, which decreases as the * lock screen is swiped away. */ -const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.25f +const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.15f /** * Dismiss amount at which to complete the keyguard exit animation and hide the keyguard. @@ -85,7 +84,7 @@ const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.25f * The dismiss amount is the inverse of the notification panel expansion, which decreases as the * lock screen is swiped away. */ -const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.4f +const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.3f /** * How long the canned unlock animation takes. This is used if we are unlocking from biometric auth, @@ -112,7 +111,7 @@ const val CANNED_UNLOCK_START_DELAY = 100L * Duration for the alpha animation on the surface behind. This plays to fade in the surface during * a swipe to unlock (and to fade it back out if the swipe is cancelled). */ -const val SURFACE_BEHIND_SWIPE_FADE_DURATION_MS = 150L +const val SURFACE_BEHIND_SWIPE_FADE_DURATION_MS = 175L /** * Start delay for the surface behind animation, used so that the lockscreen can get out of the way @@ -151,12 +150,21 @@ class KeyguardUnlockAnimationController @Inject constructor( * [playingCannedAnimation] indicates whether we are playing a canned animation to show the * app/launcher behind the keyguard, vs. this being a swipe to unlock where the dismiss * amount drives the animation. + * * [fromWakeAndUnlock] tells us whether we are unlocking directly from AOD - in this case, * the lockscreen is dismissed instantly, so we shouldn't run any animations that rely on it * being visible. + * + * [unlockAnimationStartDelay] and [unlockAnimationDuration] provide the timing parameters + * for the canned animation (if applicable) so interested parties can sync with it. If no + * canned animation is playing, these are both 0. */ @JvmDefault - fun onUnlockAnimationStarted(playingCannedAnimation: Boolean, fromWakeAndUnlock: Boolean) {} + fun onUnlockAnimationStarted( + playingCannedAnimation: Boolean, + fromWakeAndUnlock: Boolean, + unlockAnimationStartDelay: Long, + unlockAnimationDuration: Long) {} /** * Called when the remote unlock animation ends, in all cases, canned or swipe-to-unlock. @@ -165,19 +173,6 @@ class KeyguardUnlockAnimationController @Inject constructor( */ @JvmDefault fun onUnlockAnimationFinished() {} - - /** - * Called when we begin the smartspace shared element transition, either due to an unlock - * action (biometric, etc.) or a swipe to unlock. - * - * This transition can begin BEFORE [onUnlockAnimationStarted] is called, if we are swiping - * to unlock and the surface behind the keyguard has not yet been made visible. This is - * because the lockscreen smartspace immediately begins moving towards the launcher - * smartspace location when a swipe begins, even before we start the keyguard exit remote - * animation and show the launcher itself. - */ - @JvmDefault - fun onSmartspaceSharedElementTransitionStarted() {} } /** The SmartSpace view on the lockscreen, provided by [KeyguardClockSwitchController]. */ @@ -259,8 +254,9 @@ class KeyguardUnlockAnimationController @Inject constructor( * animation plays. */ private var surfaceBehindAlpha = 1f - private var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f) - private var smartspaceAnimator = ValueAnimator.ofFloat(0f, 1f) + + @VisibleForTesting + var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f) /** * Matrix applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the @@ -281,59 +277,38 @@ class KeyguardUnlockAnimationController @Inject constructor( private var roundedCornerRadius = 0f /** - * Whether we tried to start the SmartSpace shared element transition for this unlock swipe. - * It's possible we were unable to do so (if the Launcher SmartSpace is not available), and we - * need to keep track of that so that we don't start doing it halfway through the swipe if - * Launcher becomes available suddenly. - */ - private var attemptedSmartSpaceTransitionForThisSwipe = false - - /** - * The original location of the lockscreen smartspace on the screen. - */ - private val smartspaceOriginBounds = Rect() - - /** - * The bounds to which the lockscreen smartspace is moving. This is set to the bounds of the - * launcher's smartspace prior to the transition starting. - */ - private val smartspaceDestBounds = Rect() - - /** - * From 0f to 1f, the progress of the smartspace shared element animation. 0f means the - * smartspace is at its normal position within the lock screen hierarchy, and 1f means it has - * fully animated to the location of the Launcher's smartspace. + * Whether we decided in [prepareForInWindowLauncherAnimations] that we are able to and want to + * play the in-window launcher unlock animations rather than simply animating the Launcher + * window like any other app. This can be true while [willUnlockWithSmartspaceTransition] is + * false, if the smartspace is not available or was not ready in time. */ - private var smartspaceUnlockProgress = 0f + private var willUnlockWithInWindowLauncherAnimations: Boolean = false /** - * Whether we're currently unlocking, and we're talking to Launcher to perform in-window - * animations rather than simply animating the Launcher window like any other app. This can be - * true while [unlockingWithSmartspaceTransition] is false, if the smartspace is not available - * or was not ready in time. + * Whether we decided in [prepareForInWindowLauncherAnimations] that we are able to and want to + * play the smartspace shared element animation. If true, + * [willUnlockWithInWindowLauncherAnimations] will also always be true since in-window + * animations are a prerequisite for the smartspace transition. */ - private var unlockingToLauncherWithInWindowAnimations: Boolean = false - - /** - * Whether we are currently unlocking, and the smartspace shared element transition is in - * progress. If true, we're also [unlockingToLauncherWithInWindowAnimations]. - */ - private var unlockingWithSmartspaceTransition: Boolean = false + private var willUnlockWithSmartspaceTransition: Boolean = false private val handler = Handler() init { with(surfaceBehindAlphaAnimator) { duration = SURFACE_BEHIND_SWIPE_FADE_DURATION_MS - interpolator = Interpolators.TOUCH_RESPONSE + interpolator = Interpolators.LINEAR addUpdateListener { valueAnimator: ValueAnimator -> surfaceBehindAlpha = valueAnimator.animatedValue as Float updateSurfaceBehindAppearAmount() } addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { - // If the surface alpha is 0f, it's no longer visible so we can safely be done - // with the animation even if other properties are still animating. + // If we animated the surface alpha to 0f, it means we cancelled a swipe to + // dismiss. In this case, we should ask the KeyguardViewMediator to end the + // remote animation to hide the surface behind the keyguard, but should *not* + // call onKeyguardExitRemoteAnimationFinished since that will hide the keyguard + // and unlock the device as well as hiding the surface. if (surfaceBehindAlpha == 0f) { keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation( false /* cancelled */) @@ -360,21 +335,6 @@ class KeyguardUnlockAnimationController @Inject constructor( }) } - with(smartspaceAnimator) { - duration = UNLOCK_ANIMATION_DURATION_MS - interpolator = Interpolators.TOUCH_RESPONSE - addUpdateListener { - smartspaceUnlockProgress = it.animatedValue as Float - } - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { - launcherUnlockController?.setSmartspaceVisibility(View.VISIBLE) - keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( - false /* cancelled */) - } - }) - } - // Listen for changes in the dismiss amount. keyguardStateController.addCallback(this) @@ -394,6 +354,74 @@ class KeyguardUnlockAnimationController @Inject constructor( } /** + * Whether we should be able to do the in-window launcher animations given the current state of + * the device. + */ + fun canPerformInWindowLauncherAnimations(): Boolean { + return isNexusLauncherUnderneath() && + launcherUnlockController != null && + !keyguardStateController.isDismissingFromSwipe && + // Temporarily disable for foldables since foldable launcher has two first pages, + // which breaks the in-window animation. + !isFoldable(context) + } + + /** + * Called from [KeyguardStateController] to let us know that the keyguard going away state has + * changed. + */ + override fun onKeyguardGoingAwayChanged() { + if (keyguardStateController.isKeyguardGoingAway) { + prepareForInWindowLauncherAnimations() + } + } + + /** + * Prepare for in-window Launcher unlock animations, if we're able to do so. + * + * The in-window animations consist of the staggered ring icon unlock animation, and optionally + * the shared element smartspace transition. + */ + fun prepareForInWindowLauncherAnimations() { + willUnlockWithInWindowLauncherAnimations = canPerformInWindowLauncherAnimations() + + if (!willUnlockWithInWindowLauncherAnimations) { + return + } + + // There are additional conditions under which we should not perform the smartspace + // transition specifically, so check those. + willUnlockWithSmartspaceTransition = shouldPerformSmartspaceTransition() + + var lockscreenSmartspaceBounds = Rect() + + // Grab the bounds of our lockscreen smartspace and send them to launcher so they can + // position their smartspace there initially, then animate it to its resting position. + if (willUnlockWithSmartspaceTransition) { + lockscreenSmartspaceBounds = Rect().apply { + lockscreenSmartspace!!.getBoundsOnScreen(this) + offset(lockscreenSmartspace!!.paddingLeft, lockscreenSmartspace!!.paddingTop) + } + } + + // Currently selected lockscreen smartspace page, or -1 if it's not available. + val selectedPage = + (lockscreenSmartspace as BcSmartspaceDataPlugin.SmartspaceView?)?.selectedPage ?: -1 + + try { + + // Let the launcher know to prepare for this animation. + launcherUnlockController?.prepareForUnlock( + willUnlockWithSmartspaceTransition, /* willAnimateSmartspace */ + lockscreenSmartspaceBounds, /* lockscreenSmartspaceBounds */ + selectedPage, /* selectedPage */ + ) + } catch (e: RemoteException) { + Log.e(TAG, "Remote exception in prepareForInWindowUnlockAnimations.", e) + } + } + + /** * Called from [KeyguardViewMediator] to tell us that the RemoteAnimation on the surface behind * the keyguard has started successfully. We can use these parameters to directly manipulate the * surface for the unlock gesture/animation. @@ -404,7 +432,8 @@ class KeyguardUnlockAnimationController @Inject constructor( * * [requestedShowSurfaceBehindKeyguard] indicates whether the animation started because of a * call to [KeyguardViewMediator.showSurfaceBehindKeyguard], as happens during a swipe gesture, - * as opposed to being called because the device was unlocked and the keyguard is going away. + * as opposed to being called because the device was unlocked instantly by some other means + * (fingerprint, tap, etc.) and the keyguard is going away. */ fun notifyStartSurfaceBehindRemoteAnimation( target: RemoteAnimationTarget, @@ -422,19 +451,31 @@ class KeyguardUnlockAnimationController @Inject constructor( surfaceBehindRemoteAnimationTarget = target surfaceBehindRemoteAnimationStartTime = startTime - // If we specifically requested that the surface behind be made visible, it means we are - // swiping to unlock. In that case, the surface visibility is tied to the dismiss amount, - // and we'll handle that in onKeyguardDismissAmountChanged(). If we didn't request that, the - // keyguard is being dismissed for a different reason (biometric auth, etc.) and we should - // play a canned animation to make the surface fully visible. - if (!requestedShowSurfaceBehindKeyguard) { + // If we specifically requested that the surface behind be made visible (vs. it being made + // visible because we're unlocking), then we're in the middle of a swipe-to-unlock touch + // gesture and the surface behind the keyguard should be made visible. + if (requestedShowSurfaceBehindKeyguard) { + // Fade in the surface, as long as we're not now flinging. The touch gesture ending in + // a fling during the time it takes the keyguard exit animation to start is an edge + // case race condition, and we'll handle it by playing a canned animation on the + // now-visible surface to finish unlocking. + if (!keyguardStateController.isFlingingToDismissKeyguard) { + fadeInSurfaceBehind() + } else { + playCannedUnlockAnimation() + } + } else { + // The surface was made visible since we're unlocking not from a swipe (fingerprint, + // lock icon long-press, etc). Play the full unlock animation. playCannedUnlockAnimation() } listeners.forEach { it.onUnlockAnimationStarted( playingCannedUnlockAnimation /* playingCannedAnimation */, - biometricUnlockControllerLazy.get().isWakeAndUnlock /* isWakeAndUnlock */) } + biometricUnlockControllerLazy.get().isWakeAndUnlock /* isWakeAndUnlock */, + CANNED_UNLOCK_START_DELAY /* unlockStartDelay */, + LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */) } // Finish the keyguard remote animation if the dismiss amount has crossed the threshold. // Check it here in case there is no more change to the dismiss amount after the last change @@ -443,57 +484,32 @@ class KeyguardUnlockAnimationController @Inject constructor( } /** - * Called by [KeyguardViewMediator] to let us know that the remote animation has finished, and - * we should clean up all of our state. - */ - fun notifyFinishedKeyguardExitAnimation(cancelled: Boolean) { - // Cancel any pending actions. - handler.removeCallbacksAndMessages(null) - - // Make sure we made the surface behind fully visible, just in case. It should already be - // fully visible. - setSurfaceBehindAppearAmount(1f) - launcherUnlockController?.setUnlockAmount(1f) - smartspaceDestBounds.setEmpty() - - // That target is no longer valid since the animation finished, null it out. - surfaceBehindRemoteAnimationTarget = null - surfaceBehindParams = null - - playingCannedUnlockAnimation = false - unlockingToLauncherWithInWindowAnimations = false - unlockingWithSmartspaceTransition = false - resetSmartspaceTransition() - - listeners.forEach { it.onUnlockAnimationFinished() } - } - - /** * Play a canned unlock animation to unlock the device. This is used when we were *not* swiping * to unlock using a touch gesture. If we were swiping to unlock, the animation will be driven * by the dismiss amount via [onKeyguardDismissAmountChanged]. */ - fun playCannedUnlockAnimation() { + private fun playCannedUnlockAnimation() { playingCannedUnlockAnimation = true - if (canPerformInWindowLauncherAnimations()) { - // If possible, use the neat in-window animations to unlock to the launcher. - unlockToLauncherWithInWindowAnimations() - } else if (!biometricUnlockControllerLazy.get().isWakeAndUnlock) { - // If the launcher isn't behind the keyguard, or the launcher unlock controller is not - // available, animate in the entire window. - surfaceBehindEntryAnimator.start() - } else { - setSurfaceBehindAppearAmount(1f) - keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(false) - } - // If this is a wake and unlock, hide the lockscreen immediately. In the future, we should - // animate it out nicely instead, but to the current state of wake and unlock, not hiding it - // causes a lot of issues. - // TODO(b/210016643): Not this, it looks not-ideal! - if (biometricUnlockControllerLazy.get().isWakeAndUnlock) { - keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 350) + when { + // If we're set up for in-window launcher animations, ask Launcher to play its in-window + // canned animation. + willUnlockWithInWindowLauncherAnimations -> unlockToLauncherWithInWindowAnimations() + + // If we're waking and unlocking to a non-Launcher app surface (or Launcher in-window + // animations are not available), show it immediately and end the remote animation. The + // circular light reveal will show the app surface, and it looks weird if it's moving + // around behind that. + biometricUnlockControllerLazy.get().isWakeAndUnlock -> { + setSurfaceBehindAppearAmount(1f) + keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( + false /* cancelled */) + } + + // Otherwise, we're doing a normal full-window unlock. Start this animator, which will + // scale/translate the window underneath the lockscreen. + else -> surfaceBehindEntryAnimator.start() } } @@ -502,205 +518,31 @@ class KeyguardUnlockAnimationController @Inject constructor( * transition if possible. */ private fun unlockToLauncherWithInWindowAnimations() { - // See if we can do the smartspace transition, and if so, do it! - if (prepareForSmartspaceTransition()) { - animateSmartspaceToDestination() - listeners.forEach { it.onSmartspaceSharedElementTransitionStarted() } - } - - val startDelay = Settings.Secure.getLong( - context.contentResolver, "unlock_start_delay", CANNED_UNLOCK_START_DELAY) - val duration = Settings.Secure.getLong( - context.contentResolver, "unlock_duration", LAUNCHER_ICONS_ANIMATION_DURATION_MS) - - unlockingToLauncherWithInWindowAnimations = true - prepareLauncherWorkspaceForUnlockAnimation() + setSurfaceBehindAppearAmount(1f) // Begin the animation, waiting for the shade to animate out. launcherUnlockController?.playUnlockAnimation( true /* unlocked */, - duration /* duration */, - startDelay /* startDelay */) - - handler.postDelayed({ - applyParamsToSurface( - SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( - surfaceBehindRemoteAnimationTarget!!.leash) - .withAlpha(1f) - .build()) - }, startDelay) - - if (!unlockingWithSmartspaceTransition) { - // If we are not unlocking with the smartspace transition, wait for the unlock animation - // to end and then finish the remote animation. If we are using the smartspace - // transition, it will finish the remote animation once it ends. - handler.postDelayed({ - keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( - false /* cancelled */) - }, UNLOCK_ANIMATION_DURATION_MS) - } - } - - /** - * Asks Launcher to prepare the workspace to be unlocked. This sets up the animation and makes - * the page invisible. - */ - private fun prepareLauncherWorkspaceForUnlockAnimation() { - // Tell the launcher to prepare for the animation by setting its views invisible and - // syncing the selected smartspace pages. - launcherUnlockController?.prepareForUnlock( - unlockingWithSmartspaceTransition /* willAnimateSmartspace */, - (lockscreenSmartspace as BcSmartspaceDataPlugin.SmartspaceView?)?.selectedPage ?: -1) - } - - /** - * Animates the lockscreen smartspace all the way to the launcher's smartspace location, then - * makes the launcher smartspace visible and ends the remote animation. - */ - private fun animateSmartspaceToDestination() { - smartspaceAnimator.start() - } - - /** - * Reset the lockscreen smartspace's position, and reset all state involving the smartspace - * transition. - */ - public fun resetSmartspaceTransition() { - unlockingWithSmartspaceTransition = false - smartspaceUnlockProgress = 0f - - lockscreenSmartspace?.post { - lockscreenSmartspace!!.translationX = 0f - lockscreenSmartspace!!.translationY = 0f - } - } - - /** - * Moves the lockscreen smartspace towards the launcher smartspace's position. - */ - private fun setSmartspaceProgressToDestinationBounds(progress: Float) { - if (smartspaceDestBounds.isEmpty) { - return - } - - val progressClamped = min(1f, progress) - - // Calculate the distance (relative to the origin) that we need to be for the current - // progress value. - val progressX = - (smartspaceDestBounds.left - smartspaceOriginBounds.left) * progressClamped - val progressY = - (smartspaceDestBounds.top - smartspaceOriginBounds.top) * progressClamped - - val lockscreenSmartspaceCurrentBounds = Rect().also { - lockscreenSmartspace!!.getBoundsOnScreen(it) - } - - // Figure out how far that is from our present location on the screen. This approach - // compensates for the fact that our parent container is also translating to animate out. - val dx = smartspaceOriginBounds.left + progressX - - lockscreenSmartspaceCurrentBounds.left - val dy = smartspaceOriginBounds.top + progressY - - lockscreenSmartspaceCurrentBounds.top - - with(lockscreenSmartspace!!) { - translationX += dx - translationY += dy - } - } - - /** - * Update the lockscreen SmartSpace to be positioned according to the current dismiss amount. As - * the dismiss amount increases, we will increase our SmartSpace's progress to the destination - * bounds (the location of the Launcher SmartSpace). - * - * This is used by [KeyguardClockSwitchController] to keep the smartspace position updated as - * the clock is swiped away. - */ - fun updateLockscreenSmartSpacePosition() { - setSmartspaceProgressToDestinationBounds(smartspaceUnlockProgress) - } - - /** - * Asks the keyguard view to hide, using the start time from the beginning of the remote - * animation. - */ - fun hideKeyguardViewAfterRemoteAnimation() { - if (keyguardViewController.isShowing) { - // Hide the keyguard, with no fade out since we animated it away during the unlock. - keyguardViewController.hide( - surfaceBehindRemoteAnimationStartTime, - 0 /* fadeOutDuration */ - ) - } else { - Log.e(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " + - "showing. Ignoring...") - } - } + LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */, + CANNED_UNLOCK_START_DELAY /* startDelay */) - private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) { - surfaceTransactionApplier!!.scheduleApply(params) - surfaceBehindParams = params - } + // Now that the Launcher surface (with its smartspace positioned identically to ours) is + // visible, hide our smartspace. + lockscreenSmartspace!!.visibility = View.INVISIBLE - /** - * Scales in and translates up the surface behind the keyguard. This is used during unlock - * animations and swipe gestures to animate the surface's entry (and exit, if the swipe is - * cancelled). - */ - fun setSurfaceBehindAppearAmount(amount: Float) { - if (surfaceBehindRemoteAnimationTarget == null) { - return - } - - if (unlockingToLauncherWithInWindowAnimations) { - // If we aren't using the canned unlock animation (which would be setting the unlock - // amount in its update listener), do it here. - if (!isPlayingCannedUnlockAnimation()) { - launcherUnlockController?.setUnlockAmount(amount) - - if (surfaceBehindParams?.alpha?.let { it < 1f } != false) { - applyParamsToSurface( - SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( - surfaceBehindRemoteAnimationTarget!!.leash) - .withAlpha(1f) - .build()) - } + // As soon as the shade has animated out of the way, finish the keyguard exit animation. The + // in-window animations in the Launcher window will end on their own. + handler.postDelayed({ + if (keyguardViewMediator.get().isShowingAndNotOccluded && + !keyguardStateController.isKeyguardGoingAway) { + Log.e(TAG, "Finish keyguard exit animation delayed Runnable ran, but we are " + + "showing and not going away.") + return@postDelayed } - } else { - // Otherwise, animate in the surface's scale/transltion. - val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height() - val scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR + - (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * - MathUtils.clamp(amount, 0f, 1f)) - - // Scale up from a point at the center-bottom of the surface. - surfaceBehindMatrix.setScale( - scaleFactor, - scaleFactor, - surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.width() / 2f, - surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y - ) - // Translate up from the bottom. - surfaceBehindMatrix.postTranslate( - 0f, - surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount) - ) - - // If we're snapping the keyguard back, immediately begin fading it out. - val animationAlpha = - if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount - else surfaceBehindAlpha - - applyParamsToSurface( - SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( - surfaceBehindRemoteAnimationTarget!!.leash) - .withMatrix(surfaceBehindMatrix) - .withCornerRadius(roundedCornerRadius) - .withAlpha(animationAlpha) - .build()) - } + keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( + false /* cancelled */) + }, CANNED_UNLOCK_START_DELAY) } /** @@ -738,7 +580,7 @@ class KeyguardUnlockAnimationController @Inject constructor( return } - if (keyguardViewController.isShowing) { + if (keyguardViewController.isShowing && !playingCannedUnlockAnimation) { showOrHideSurfaceIfDismissAmountThresholdsReached() // If the surface is visible or it's about to be, start updating its appearance to @@ -750,11 +592,6 @@ class KeyguardUnlockAnimationController @Inject constructor( updateSurfaceBehindAppearAmount() } } - - // The end of the SmartSpace transition can occur after the keyguard is hidden (when we tell - // Launcher's SmartSpace to become visible again), so update it even if the keyguard view is - // no longer showing. - applyDismissAmountToSmartspaceTransition() } /** @@ -775,18 +612,16 @@ class KeyguardUnlockAnimationController @Inject constructor( return } + if (!keyguardStateController.isShowing) { + return + } + val dismissAmount = keyguardStateController.dismissAmount + if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD && - !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) { - // We passed the threshold, and we're not yet showing the surface behind the - // keyguard. Animate it in. - if (!unlockingToLauncherWithInWindowAnimations && - canPerformInWindowLauncherAnimations()) { - unlockingToLauncherWithInWindowAnimations = true - prepareLauncherWorkspaceForUnlockAnimation() - } + !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) { + keyguardViewMediator.get().showSurfaceBehindKeyguard() - fadeInSurfaceBehind() } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD && keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) { // We're no longer past the threshold but we are showing the surface. Animate it @@ -828,60 +663,103 @@ class KeyguardUnlockAnimationController @Inject constructor( } /** - * Updates flags related to the SmartSpace transition in response to a change in keyguard - * dismiss amount, and also updates the SmartSpaceTransitionController, which will let Launcher - * know if it needs to do something as a result. + * Scales in and translates up the surface behind the keyguard. This is used during unlock + * animations and swipe gestures to animate the surface's entry (and exit, if the swipe is + * cancelled). */ - private fun applyDismissAmountToSmartspaceTransition() { - if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) { + fun setSurfaceBehindAppearAmount(amount: Float) { + if (surfaceBehindRemoteAnimationTarget == null) { return } - // If we are playing the canned animation, the smartspace is being animated directly between - // its original location and the location of the launcher smartspace by smartspaceAnimator. - // We can ignore the dismiss amount, which is caused by panel height changes as the panel is - // flung away. - if (playingCannedUnlockAnimation) { - return - } + // Otherwise, animate in the surface's scale/transltion. + val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height() + val scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR + + (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * + MathUtils.clamp(amount, 0f, 1f)) + + // Scale up from a point at the center-bottom of the surface. + surfaceBehindMatrix.setScale( + scaleFactor, + scaleFactor, + surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.width() / 2f, + surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y + ) + + // Translate up from the bottom. + surfaceBehindMatrix.postTranslate( + 0f, + surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount) + ) + + // If we're snapping the keyguard back, immediately begin fading it out. + val animationAlpha = + if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount + else surfaceBehindAlpha + + applyParamsToSurface( + SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( + surfaceBehindRemoteAnimationTarget!!.leash) + .withMatrix(surfaceBehindMatrix) + .withCornerRadius(roundedCornerRadius) + .withAlpha(animationAlpha) + .build()) + } - val dismissAmount = keyguardStateController.dismissAmount + /** + * Called by [KeyguardViewMediator] to let us know that the remote animation has finished, and + * we should clean up all of our state. + * + * This is generally triggered by us, calling + * [KeyguardViewMediator.finishSurfaceBehindRemoteAnimation]. + */ + fun notifyFinishedKeyguardExitAnimation(cancelled: Boolean) { + // Cancel any pending actions. + handler.removeCallbacksAndMessages(null) - // If we've begun a swipe, and haven't yet tried doing the SmartSpace transition, do that - // now. - if (!attemptedSmartSpaceTransitionForThisSwipe && - keyguardViewController.isShowing && - dismissAmount > 0f && - dismissAmount < 1f) { - attemptedSmartSpaceTransitionForThisSwipe = true + // Make sure we made the surface behind fully visible, just in case. It should already be + // fully visible. If the launcher is doing its own animation, let it continue without + // forcing it to 1f. + setSurfaceBehindAppearAmount(1f) + launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */) - if (prepareForSmartspaceTransition()) { - unlockingWithSmartspaceTransition = true + // That target is no longer valid since the animation finished, null it out. + surfaceBehindRemoteAnimationTarget = null + surfaceBehindParams = null - // Ensure that the smartspace is invisible if we're doing the transition, and - // visible if we aren't. - launcherUnlockController?.setSmartspaceVisibility( - if (unlockingWithSmartspaceTransition) View.INVISIBLE else View.VISIBLE) + playingCannedUnlockAnimation = false + willUnlockWithInWindowLauncherAnimations = false + willUnlockWithSmartspaceTransition = false - if (unlockingWithSmartspaceTransition) { - listeners.forEach { it.onSmartspaceSharedElementTransitionStarted() } - } - } - } else if (attemptedSmartSpaceTransitionForThisSwipe && - (dismissAmount == 0f || dismissAmount == 1f)) { - attemptedSmartSpaceTransitionForThisSwipe = false - unlockingWithSmartspaceTransition = false - launcherUnlockController?.setSmartspaceVisibility(View.VISIBLE) - } + // The lockscreen surface is gone, so it is now safe to re-show the smartspace. + lockscreenSmartspace?.visibility = View.VISIBLE + + listeners.forEach { it.onUnlockAnimationFinished() } + } + + /** + * Asks the keyguard view to hide, using the start time from the beginning of the remote + * animation. + */ + fun hideKeyguardViewAfterRemoteAnimation() { + if (keyguardViewController.isShowing) { + // Hide the keyguard, with no fade out since we animated it away during the unlock. - if (unlockingWithSmartspaceTransition) { - val swipedFraction: Float = keyguardStateController.dismissAmount - val progress = swipedFraction / DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - smartspaceUnlockProgress = progress - setSmartspaceProgressToDestinationBounds(smartspaceUnlockProgress) + keyguardViewController.hide( + surfaceBehindRemoteAnimationStartTime, + 0 /* fadeOutDuration */ + ) + } else { + Log.e(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " + + "showing. Ignoring...") } } + private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) { + surfaceTransactionApplier!!.scheduleApply(params) + surfaceBehindParams = params + } + private fun fadeInSurfaceBehind() { surfaceBehindAlphaAnimator.cancel() surfaceBehindAlphaAnimator.start() @@ -892,14 +770,8 @@ class KeyguardUnlockAnimationController @Inject constructor( surfaceBehindAlphaAnimator.reverse() } - /** - * Prepare for the smartspace shared element transition, if possible, by figuring out where we - * are animating from/to. - * - * Return true if we'll be able to do the smartspace transition, or false if conditions are not - * right to do it right now. - */ - private fun prepareForSmartspaceTransition(): Boolean { + + private fun shouldPerformSmartspaceTransition(): Boolean { // Feature is disabled, so we don't want to. if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) { return false @@ -940,45 +812,22 @@ class KeyguardUnlockAnimationController @Inject constructor( return false } - unlockingWithSmartspaceTransition = true - smartspaceDestBounds.setEmpty() - - // Assuming we were able to retrieve the launcher's state, start the lockscreen - // smartspace at 0, 0, and save its starting bounds. - with(lockscreenSmartspace!!) { - translationX = 0f - translationY = 0f - getBoundsOnScreen(smartspaceOriginBounds) - } - - // Set the destination bounds to the launcher smartspace's bounds, offset by any - // padding on our smartspace. - with(smartspaceDestBounds) { - set(launcherSmartspaceState!!.boundsOnScreen) - offset(-lockscreenSmartspace!!.paddingLeft, -lockscreenSmartspace!!.paddingTop) + // We started to swipe to dismiss, but now we're doing a fling animation to complete the + // dismiss. In this case, the smartspace swiped away with the rest of the keyguard, so don't + // do the shared element transition. + if (keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture) { + return false } return true } /** - * Whether we should be able to do the in-window launcher animations given the current state of - * the device. - */ - fun canPerformInWindowLauncherAnimations(): Boolean { - return isNexusLauncherUnderneath() && - launcherUnlockController != null && - // Temporarily disable for foldables since foldable launcher has two first pages, - // which breaks the in-window animation. - !isFoldable(context) - } - - /** * Whether we are currently in the process of unlocking the keyguard, and we are performing the * shared element SmartSpace transition. */ fun isUnlockingWithSmartSpaceTransition(): Boolean { - return unlockingWithSmartspaceTransition + return willUnlockWithSmartspaceTransition } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 29e940f24df6..10ea1e06c6d7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -17,6 +17,8 @@ package com.android.systemui.keyguard; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; +import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; +import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN; @@ -119,7 +121,6 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeDepthController; @@ -2307,8 +2308,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, int flags = 0; if (mKeyguardViewControllerLazy.get().shouldDisableWindowAnimationsForUnlock() || mWakeAndUnlocking && !mWallpaperSupportsAmbientMode) { - flags |= WindowManagerPolicyConstants - .KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; + flags |= KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; } if (mKeyguardViewControllerLazy.get().isGoingToNotificationShade() || mWakeAndUnlocking && mWallpaperSupportsAmbientMode) { @@ -2324,6 +2324,15 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, .KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS; } + // If we are unlocking to the launcher, clear the snapshot so that any changes as part + // of the in-window animations are reflected. This is needed even if we're not actually + // playing in-window animations for this particular unlock since a previous unlock might + // have changed the Launcher state. + if (mWakeAndUnlocking + && KeyguardUnlockAnimationController.Companion.isNexusLauncherUnderneath()) { + flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; + } + mUpdateMonitor.setKeyguardGoingAway(true); mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true); @@ -2628,9 +2637,18 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mSurfaceBehindRemoteAnimationRequested = true; try { - ActivityTaskManager.getService().keyguardGoingAway( - WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS - | WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER); + int flags = KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS + | KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; + + // If we are unlocking to the launcher, clear the snapshot so that any changes as part + // of the in-window animations are reflected. This is needed even if we're not actually + // playing in-window animations for this particular unlock since a previous unlock might + // have changed the Launcher state. + if (KeyguardUnlockAnimationController.Companion.isNexusLauncherUnderneath()) { + flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; + } + + ActivityTaskManager.getService().keyguardGoingAway(flags); mKeyguardStateController.notifyKeyguardGoingAway(true); } catch (RemoteException e) { mSurfaceBehindRemoteAnimationRequested = false; @@ -2660,7 +2678,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } /** If it's running, finishes the RemoteAnimation on the surface behind the keyguard. */ - public void finishSurfaceBehindRemoteAnimation(boolean cancelled) { + void finishSurfaceBehindRemoteAnimation(boolean cancelled) { if (!mSurfaceBehindRemoteAnimationRunning) { return; } @@ -2796,12 +2814,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking"); mWakeAndUnlocking = true; - // We're going to animate in the Launcher, so ask WM to clear the task snapshot so we don't - // initially display an old snapshot with all of the icons visible. We're System UI, so - // we're allowed to pass in null to ask WM to find the home activity for us to prevent - // needing to IPC to Launcher. - ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(null /* homeActivity */); - keyguardDone(); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 24671696f3bd..f72f1bb47468 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -218,6 +218,18 @@ public class LogModule { return factory.create("MediaTimeout", 100); } + /** + * Provides a buffer for our connections and disconnections to MediaBrowserService. + * + * See {@link com.android.systemui.media.ResumeMediaBrowser}. + */ + @Provides + @SysUISingleton + @MediaBrowserLog + public static LogBuffer provideMediaBrowserBuffer(LogBufferFactory factory) { + return factory.create("MediaBrowser", 100); + } + /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */ @Provides @SysUISingleton diff --git a/services/tests/servicestests/aidl/com/android/servicestests/aidl/ICmdReceiverService.aidl b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java index d96450478f90..1d7ba94af4ed 100644 --- a/services/tests/servicestests/aidl/com/android/servicestests/aidl/ICmdReceiverService.aidl +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,22 @@ * limitations under the License. */ -package com.android.servicestests.aidl; +package com.android.systemui.log.dagger; -interface ICmdReceiverService { - void finishActivity(); -}
\ No newline at end of file +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** + * A {@link LogBuffer} for {@link com.android.systemui.media.ResumeMediaBrowser} + */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface MediaBrowserLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt index 5a214d1cd5e0..a6b4f1d2a9d0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt @@ -21,24 +21,41 @@ import android.animation.ValueAnimator.AnimatorUpdateListener import android.animation.ValueAnimator import android.content.Context import android.content.res.ColorStateList +import android.graphics.drawable.GradientDrawable import com.android.internal.R import com.android.internal.annotations.VisibleForTesting import com.android.settingslib.Utils import com.android.systemui.monet.ColorScheme +import com.android.systemui.util.getColorWithAlpha /** - * ColorTransition is responsible for managing the animation between two specific colors. + * A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme] + * is triggered. + */ +interface ColorTransition { + fun updateColorScheme(scheme: ColorScheme?) +} + +/** A generic implementation of [ColorTransition] so that we can define a factory method. */ +open class GenericColorTransition( + private val applyTheme: (ColorScheme?) -> Unit +) : ColorTransition { + override fun updateColorScheme(scheme: ColorScheme?) = applyTheme(scheme) +} + +/** + * A [ColorTransition] that animates between two specific colors. * It uses a ValueAnimator to execute the animation and interpolate between the source color and * the target color. * * Selection of the target color from the scheme, and application of the interpolated color * are delegated to callbacks. */ -open class ColorTransition( +open class AnimatingColorTransition( private val defaultColor: Int, private val extractColor: (ColorScheme) -> Int, private val applyColor: (Int) -> Unit -) : AnimatorUpdateListener { +) : AnimatorUpdateListener, ColorTransition { private val argbEvaluator = ArgbEvaluator() private val valueAnimator = buildAnimator() @@ -53,7 +70,7 @@ open class ColorTransition( applyColor(currentColor) } - fun updateColorScheme(scheme: ColorScheme?) { + override fun updateColorScheme(scheme: ColorScheme?) { val newTargetColor = if (scheme == null) defaultColor else extractColor(scheme) if (newTargetColor != targetColor) { sourceColor = currentColor @@ -76,7 +93,9 @@ open class ColorTransition( } } -typealias ColorTransitionFactory = (Int, (ColorScheme) -> Int, (Int) -> Unit) -> ColorTransition +typealias AnimatingColorTransitionFactory = + (Int, (ColorScheme) -> Int, (Int) -> Unit) -> AnimatingColorTransition +typealias GenericColorTransitionFactory = ((ColorScheme?) -> Unit) -> GenericColorTransition /** * ColorSchemeTransition constructs a ColorTransition for each color in the scheme @@ -86,27 +105,26 @@ typealias ColorTransitionFactory = (Int, (ColorScheme) -> Int, (Int) -> Unit) -> class ColorSchemeTransition internal constructor( private val context: Context, mediaViewHolder: MediaViewHolder, - colorTransitionFactory: ColorTransitionFactory + animatingColorTransitionFactory: AnimatingColorTransitionFactory, + genericColorTransitionFactory: GenericColorTransitionFactory ) { constructor(context: Context, mediaViewHolder: MediaViewHolder) : - this(context, mediaViewHolder, ::ColorTransition) + this(context, mediaViewHolder, ::AnimatingColorTransition, ::GenericColorTransition) val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95) - val surfaceColor = colorTransitionFactory( + val surfaceColor = animatingColorTransitionFactory( bgColor, ::surfaceFromScheme ) { surfaceColor -> val colorList = ColorStateList.valueOf(surfaceColor) mediaViewHolder.player.backgroundTintList = colorList - mediaViewHolder.albumView.foregroundTintList = colorList - mediaViewHolder.albumView.backgroundTintList = colorList mediaViewHolder.seamlessIcon.imageTintList = colorList mediaViewHolder.seamlessText.setTextColor(surfaceColor) mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor) } - val accentPrimary = colorTransitionFactory( + val accentPrimary = animatingColorTransitionFactory( loadDefaultColor(R.attr.textColorPrimary), ::accentPrimaryFromScheme ) { accentPrimary -> @@ -116,7 +134,7 @@ class ColorSchemeTransition internal constructor( mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary) } - val textPrimary = colorTransitionFactory( + val textPrimary = animatingColorTransitionFactory( loadDefaultColor(R.attr.textColorPrimary), ::textPrimaryFromScheme ) { textPrimary -> @@ -132,28 +150,65 @@ class ColorSchemeTransition internal constructor( mediaViewHolder.gutsViewHolder.setTextPrimaryColor(textPrimary) } - val textPrimaryInverse = colorTransitionFactory( + val textPrimaryInverse = animatingColorTransitionFactory( loadDefaultColor(R.attr.textColorPrimaryInverse), ::textPrimaryInverseFromScheme ) { textPrimaryInverse -> mediaViewHolder.actionPlayPause.imageTintList = ColorStateList.valueOf(textPrimaryInverse) } - val textSecondary = colorTransitionFactory( + val textSecondary = animatingColorTransitionFactory( loadDefaultColor(R.attr.textColorSecondary), ::textSecondaryFromScheme ) { textSecondary -> mediaViewHolder.artistText.setTextColor(textSecondary) } - val textTertiary = colorTransitionFactory( + val textTertiary = animatingColorTransitionFactory( loadDefaultColor(R.attr.textColorTertiary), ::textTertiaryFromScheme ) { textTertiary -> mediaViewHolder.seekBar.progressBackgroundTintList = ColorStateList.valueOf(textTertiary) } + // Note: This background gradient currently doesn't animate between colors. + val backgroundGradient = genericColorTransitionFactory { scheme -> + val defaultTintColor = ColorStateList.valueOf(bgColor) + if (scheme == null) { + mediaViewHolder.albumView.foregroundTintList = defaultTintColor + mediaViewHolder.albumView.backgroundTintList = defaultTintColor + return@genericColorTransitionFactory + } + + // If there's no album art, just hide the gradient so we show the solid background. + val showGradient = mediaViewHolder.albumView.drawable != null + val startColor = getColorWithAlpha( + backgroundStartFromScheme(scheme), + alpha = if (showGradient) .25f else 0f + ) + val endColor = getColorWithAlpha( + backgroundEndFromScheme(scheme), + alpha = if (showGradient) .90f else 0f + ) + val gradientColors = intArrayOf(startColor, endColor) + + val foregroundGradient = mediaViewHolder.albumView.foreground?.mutate() + if (foregroundGradient is GradientDrawable) { + foregroundGradient.colors = gradientColors + } + val backgroundGradient = mediaViewHolder.albumView.background?.mutate() + if (backgroundGradient is GradientDrawable) { + backgroundGradient.colors = gradientColors + } + } + val colorTransitions = arrayOf( - surfaceColor, accentPrimary, textPrimary, - textPrimaryInverse, textSecondary, textTertiary) + surfaceColor, + accentPrimary, + textPrimary, + textPrimaryInverse, + textSecondary, + textTertiary, + backgroundGradient + ) private fun loadDefaultColor(id: Int): Int { return Utils.getColorAttr(context, id).defaultColor diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 7b72ab732543..38128bfb70e7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -127,7 +127,7 @@ class MediaCarouselController @Inject constructor( private val visualStabilityCallback: OnReorderingAllowedListener private var needsReordering: Boolean = false private var keysNeedRemoval = mutableSetOf<String>() - protected var shouldScrollToActivePlayer: Boolean = false + var shouldScrollToActivePlayer: Boolean = false private var isRtl: Boolean = false set(value) { if (value != field) { @@ -413,9 +413,7 @@ class MediaCarouselController @Inject constructor( .indexOfFirst { key -> it == key } mediaCarouselScrollHandler .scrollToPlayer(previousVisibleIndex, activeMediaIndex) - } ?: { - mediaCarouselScrollHandler.scrollToPlayer(destIndex = activeMediaIndex) - } + } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = activeMediaIndex) } } } @@ -432,7 +430,7 @@ class MediaCarouselController @Inject constructor( val curVisibleMediaKey = MediaPlayerData.playerKeys() .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) if (existingPlayer == null) { - var newPlayer = mediaControlPanelFactory.get() + val newPlayer = mediaControlPanelFactory.get() newPlayer.attachPlayer(MediaViewHolder.create( LayoutInflater.from(context), mediaContent)) newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions @@ -480,7 +478,7 @@ class MediaCarouselController @Inject constructor( MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey) } - var newRecs = mediaControlPanelFactory.get() + val newRecs = mediaControlPanelFactory.get() newRecs.attachRecommendation( RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)) newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions @@ -1048,7 +1046,5 @@ internal object MediaPlayerData { return false } - fun isSsReactivated(key: String): Boolean = mediaData.get(key)?.let { - it.isSsReactivated - } ?: false + fun isSsReactivated(key: String): Boolean = mediaData.get(key)?.isSsReactivated ?: false }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt b/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt index 97c6014c91bd..5e767b0458b9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt @@ -35,3 +35,9 @@ internal fun textSecondaryFromScheme(scheme: ColorScheme) = scheme.neutral2[3] / /** Returns the tertiary text color for media controls based on the scheme. */ internal fun textTertiaryFromScheme(scheme: ColorScheme) = scheme.neutral2[5] // N2-400 + +/** Returns the color for the start of the background gradient based on the scheme. */ +internal fun backgroundStartFromScheme(scheme: ColorScheme) = scheme.accent2[8] // A2-700 + +/** Returns the color for the end of the background gradient based on the scheme. */ +internal fun backgroundEndFromScheme(scheme: ColorScheme) = scheme.accent1[8] // A1-700 diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index d9ee8f3f06b4..5a32e91b1d18 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -18,6 +18,8 @@ package com.android.systemui.media; import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS; +import static com.android.systemui.media.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS; + import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorSet; @@ -102,7 +104,6 @@ public class MediaControlPanel { + ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity"; private static final String EXTRAS_SMARTSPACE_INTENT = "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT"; - private static final int MEDIA_RECOMMENDATION_MAX_NUM = 3; private static final String KEY_SMARTSPACE_ARTIST_NAME = "artist_name"; private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND"; private static final String KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME"; @@ -949,16 +950,14 @@ public class MediaControlPanel { return; } + if (!data.isValid()) { + Log.e(TAG, "Received an invalid recommendation list; returning"); + return; + } + mSmartspaceId = SmallHash.hash(data.getTargetId()); mPackageName = data.getPackageName(); mInstanceId = data.getInstanceId(); - TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations(); - - List<SmartspaceAction> mediaRecommendationList = data.getRecommendations(); - if (mediaRecommendationList == null || mediaRecommendationList.isEmpty()) { - Log.w(TAG, "Empty media recommendations"); - return; - } // Set up recommendation card's header. ApplicationInfo applicationInfo; @@ -994,6 +993,7 @@ public class MediaControlPanel { } // Set up media rec card's tap action if applicable. + TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations(); setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(), /* interactedSubcardRank */ -1); // Set up media rec card's accessibility label. @@ -1002,29 +1002,20 @@ public class MediaControlPanel { List<ImageView> mediaCoverItems = mRecommendationViewHolder.getMediaCoverItems(); List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers(); - int mediaRecommendationNum = Math.min(mediaRecommendationList.size(), - MEDIA_RECOMMENDATION_MAX_NUM); + List<SmartspaceAction> recommendations = data.getValidRecommendations(); boolean hasTitle = false; boolean hasSubtitle = false; - int uiComponentIndex = 0; - for (int itemIndex = 0; - itemIndex < mediaRecommendationNum && uiComponentIndex < mediaRecommendationNum; - itemIndex++) { - SmartspaceAction recommendation = mediaRecommendationList.get(itemIndex); - if (recommendation.getIcon() == null) { - Log.w(TAG, "No media cover is provided. Skipping this item..."); - continue; - } + for (int itemIndex = 0; itemIndex < NUM_REQUIRED_RECOMMENDATIONS; itemIndex++) { + SmartspaceAction recommendation = recommendations.get(itemIndex); // Set up media item cover. - ImageView mediaCoverImageView = mediaCoverItems.get(uiComponentIndex); + ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex); mediaCoverImageView.setImageIcon(recommendation.getIcon()); // Set up the media item's click listener if applicable. - ViewGroup mediaCoverContainer = mediaCoverContainers.get(uiComponentIndex); - setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation, - uiComponentIndex); + ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex); + setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation, itemIndex); // Bubble up the long-click event to the card. mediaCoverContainer.setOnLongClickListener(v -> { View parent = (View) v.getParent(); @@ -1053,8 +1044,7 @@ public class MediaControlPanel { // Set up title CharSequence title = recommendation.getTitle(); hasTitle |= !TextUtils.isEmpty(title); - TextView titleView = - mRecommendationViewHolder.getMediaTitles().get(uiComponentIndex); + TextView titleView = mRecommendationViewHolder.getMediaTitles().get(itemIndex); titleView.setText(title); // Set up subtitle @@ -1062,13 +1052,10 @@ public class MediaControlPanel { boolean shouldShowSubtitleText = !TextUtils.isEmpty(title); CharSequence subtitle = shouldShowSubtitleText ? recommendation.getSubtitle() : ""; hasSubtitle |= !TextUtils.isEmpty(subtitle); - TextView subtitleView = - mRecommendationViewHolder.getMediaSubtitles().get(uiComponentIndex); + TextView subtitleView = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex); subtitleView.setText(subtitle); - - uiComponentIndex++; } - mSmartspaceMediaItemsCount = uiComponentIndex; + mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS; // If there's no subtitles and/or titles for any of the albums, hide those views. ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); @@ -1301,7 +1288,7 @@ public class MediaControlPanel { } logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT, interactedSubcardRank, - getSmartspaceSubCardCardinality()); + mSmartspaceMediaItemsCount); if (shouldSmartspaceRecItemOpenInForeground(action)) { // Request to unlock the device if the activity needs to be opened in foreground. @@ -1386,13 +1373,4 @@ public class MediaControlPanel { interactedSubcardRank, interactedSubcardCardinality); } - - private int getSmartspaceSubCardCardinality() { - if (!mMediaCarouselController.getMediaCarouselScrollHandler().getQsExpanded() - && mSmartspaceMediaItemsCount > 3) { - return 3; - } - - return mSmartspaceMediaItemsCount; - } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt index 647d3efa5916..81efdf591b41 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt @@ -166,7 +166,7 @@ class MediaDataFilter @Inject constructor( shouldPrioritizeMutable = true } - if (!data.isValid) { + if (!data.isValid()) { Log.d(TAG, "Invalid recommendation data. Skip showing the rec card") return } @@ -203,7 +203,6 @@ class MediaDataFilter @Inject constructor( if (smartspaceMediaData.isActive) { smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( targetId = smartspaceMediaData.targetId, - isValid = smartspaceMediaData.isValid, instanceId = smartspaceMediaData.instanceId) } listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) } @@ -260,7 +259,6 @@ class MediaDataFilter @Inject constructor( } smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( targetId = smartspaceMediaData.targetId, - isValid = smartspaceMediaData.isValid, instanceId = smartspaceMediaData.instanceId) mediaDataManager.dismissSmartspaceRecommendation(smartspaceMediaData.targetId, delay = 0L) @@ -272,13 +270,13 @@ class MediaDataFilter @Inject constructor( */ fun hasActiveMediaOrRecommendation() = userEntries.any { it.value.active } || - (smartspaceMediaData.isActive && smartspaceMediaData.isValid) + (smartspaceMediaData.isActive && smartspaceMediaData.isValid()) /** * Are there any media entries we should display? */ fun hasAnyMediaOrRecommendation() = userEntries.isNotEmpty() || - (smartspaceMediaData.isActive && smartspaceMediaData.isValid) + (smartspaceMediaData.isActive && smartspaceMediaData.isValid()) /** * Are there any media notifications active (excluding the recommendation)? diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index b2751cec5d9e..0a4455658b6b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -105,7 +105,6 @@ private val LOADING = MediaData( internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData( targetId = "INVALID", isActive = false, - isValid = false, packageName = "INVALID", cardAction = null, recommendations = emptyList(), @@ -261,6 +260,8 @@ class MediaDataManager( // Set up links back into the pipeline for listeners that need to send events upstream. mediaTimeoutListener.timeoutCallback = { key: String, timedOut: Boolean -> setTimedOut(key, timedOut) } + mediaTimeoutListener.stateCallback = { key: String, state: PlaybackState -> + updateState(key, state) } mediaResumeListener.setManager(this) mediaDataFilter.mediaDataManager = this @@ -502,6 +503,21 @@ class MediaDataManager( } } + /** + * Called when the player's [PlaybackState] has been updated with new actions and/or state + */ + private fun updateState(key: String, state: PlaybackState) { + mediaEntries.get(key)?.let { + val actions = createActionsFromState(it.packageName, + mediaControllerFactory.create(it.token), UserHandle(it.userId)) + val data = it.copy( + semanticActions = actions, + isPlaying = isPlayingState(state.state)) + if (DEBUG) Log.d(TAG, "State updated outside of notification") + onMediaDataLoaded(key, key, data) + } + } + private fun removeEntry(key: String) { mediaEntries.remove(key)?.let { logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId) @@ -534,7 +550,7 @@ class MediaDataManager( * connection session. */ fun dismissSmartspaceRecommendation(key: String, delay: Long) { - if (smartspaceMediaData.targetId != key || !smartspaceMediaData.isValid) { + if (smartspaceMediaData.targetId != key || !smartspaceMediaData.isValid()) { // If this doesn't match, or we've already invalidated the data, no action needed return } @@ -673,11 +689,8 @@ class MediaDataManager( // Otherwise, use the notification actions var actionIcons: List<MediaAction> = emptyList() var actionsToShowCollapsed: List<Int> = emptyList() - var semanticActions: MediaButton? = null - if (mediaFlags.areMediaSessionActionsEnabled(sbn.packageName, sbn.user) && - mediaController.playbackState != null) { - semanticActions = createActionsFromState(sbn.packageName, mediaController) - } else { + val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user) + if (semanticActions == null) { val actions = createActionsFromNotification(sbn) actionIcons = actions.first actionsToShowCollapsed = actions.second @@ -789,13 +802,17 @@ class MediaDataManager( * @return a Pair consisting of a list of media actions, and a list of ints representing which * of those actions should be shown in the compact player */ - private fun createActionsFromState(packageName: String, controller: MediaController): - MediaButton? { + private fun createActionsFromState( + packageName: String, + controller: MediaController, + user: UserHandle + ): MediaButton? { val state = controller.playbackState - if (state == null) { - return MediaButton() + if (state == null || !mediaFlags.areMediaSessionActionsEnabled(packageName, user)) { + return null } - // First, check for} standard actions + + // First, check for standard actions val playOrPause = if (isConnectingState(state.state)) { // Spinner needs to be animating to render anything. Start it here. val drawable = context.getDrawable( @@ -1222,7 +1239,6 @@ class MediaDataManager( return SmartspaceMediaData( targetId = target.smartspaceTargetId, isActive = isActive, - isValid = true, packageName = it, cardAction = target.baseAction, recommendations = target.iconGrid, diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt index 7f25642be5ee..cc06b6c67879 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt @@ -64,6 +64,11 @@ class MediaResumeListener @Inject constructor( private lateinit var mediaDataManager: MediaDataManager private var mediaBrowser: ResumeMediaBrowser? = null + set(value) { + // Always disconnect the old browser -- see b/225403871. + field?.disconnect() + field = value + } private var currentUserId: Int = context.userId @VisibleForTesting @@ -189,7 +194,6 @@ class MediaResumeListener @Inject constructor( if (useMediaResumption) { // If this had been started from a resume state, disconnect now that it's live if (!key.equals(oldKey)) { - mediaBrowser?.disconnect() mediaBrowser = null } // If we don't have a resume action, check if we haven't already @@ -223,7 +227,6 @@ class MediaResumeListener @Inject constructor( Log.d(TAG, "Testing if we can connect to $componentName") // Set null action to prevent additional attempts to connect mediaDataManager.setResumeAction(key, null) - mediaBrowser?.disconnect() mediaBrowser = mediaBrowserFactory.create( object : ResumeMediaBrowser.Callback() { override fun onConnected() { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt index 8c6710a6fd68..fc8d38d59d59 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt @@ -55,6 +55,13 @@ class MediaTimeoutListener @Inject constructor( */ lateinit var timeoutCallback: (String, Boolean) -> Unit + /** + * Callback representing that a media object [PlaybackState] has changed. + * @param key Media control unique identifier + * @param state The new [PlaybackState] + */ + lateinit var stateCallback: (String, PlaybackState) -> Unit + override fun onMediaDataLoaded( key: String, oldKey: String?, @@ -85,17 +92,17 @@ class MediaTimeoutListener @Inject constructor( } reusedListener?.let { - val wasPlaying = it.playing ?: false + val wasPlaying = it.isPlaying() logger.logUpdateListener(key, wasPlaying) it.mediaData = data it.key = key mediaListeners[key] = it - if (wasPlaying != it.playing) { + if (wasPlaying != it.isPlaying()) { // If a player becomes active because of a migration, we'll need to broadcast // its state. Doing it now would lead to reentrant callbacks, so let's wait // until we're done. mainExecutor.execute { - if (mediaListeners[key]?.playing == true) { + if (mediaListeners[key]?.isPlaying() == true) { logger.logDelayedUpdate(key) timeoutCallback.invoke(key, false /* timedOut */) } @@ -121,7 +128,7 @@ class MediaTimeoutListener @Inject constructor( ) : MediaController.Callback() { var timedOut = false - var playing: Boolean? = null + var lastState: PlaybackState? = null var resumption: Boolean? = null var destroyed = false @@ -145,6 +152,9 @@ class MediaTimeoutListener @Inject constructor( private var mediaController: MediaController? = null private var cancellation: Runnable? = null + fun Int.isPlaying() = isPlayingState(this) + fun isPlaying() = lastState?.state?.isPlaying() ?: false + init { mediaData = data } @@ -175,16 +185,26 @@ class MediaTimeoutListener @Inject constructor( private fun processState(state: PlaybackState?, dispatchEvents: Boolean) { logger.logPlaybackState(key, state) - val isPlaying = state != null && isPlayingState(state.state) + val playingStateSame = (state?.state?.isPlaying() == isPlaying()) + val actionsSame = (lastState?.actions == state?.actions) && + areCustomActionListsEqual(lastState?.customActions, state?.customActions) val resumptionChanged = resumption != mediaData.resumption - if (playing == isPlaying && playing != null && !resumptionChanged) { + + lastState = state + + if ((!actionsSame || !playingStateSame) && state != null && dispatchEvents) { + logger.logStateCallback(key) + stateCallback.invoke(key, state) + } + + if (playingStateSame && !resumptionChanged) { return } - playing = isPlaying resumption = mediaData.resumption - if (!isPlaying) { - logger.logScheduleTimeout(key, isPlaying, resumption!!) + val playing = isPlaying() + if (!playing) { + logger.logScheduleTimeout(key, playing, resumption!!) if (cancellation != null && !resumptionChanged) { // if the media changed resume state, we'll need to adjust the timeout length logger.logCancelIgnored(key) @@ -220,4 +240,50 @@ class MediaTimeoutListener @Inject constructor( cancellation = null } } + + private fun areCustomActionListsEqual( + first: List<PlaybackState.CustomAction>?, + second: List<PlaybackState.CustomAction>? + ): Boolean { + // Same object, or both null + if (first === second) { + return true + } + + // Only one null, or different number of actions + if ((first == null || second == null) || (first.size != second.size)) { + return false + } + + // Compare individual actions + first.asSequence().zip(second.asSequence()).forEach { (firstAction, secondAction) -> + if (!areCustomActionsEqual(firstAction, secondAction)) { + return false + } + } + return true + } + + private fun areCustomActionsEqual( + firstAction: PlaybackState.CustomAction, + secondAction: PlaybackState.CustomAction + ): Boolean { + if (firstAction.action != secondAction.action || + firstAction.name != secondAction.name || + firstAction.icon != secondAction.icon) { + return false + } + + if ((firstAction.extras == null) != (secondAction.extras == null)) { + return false + } + if (firstAction.extras != null) { + firstAction.extras.keySet().forEach { key -> + if (firstAction.extras.get(key) != secondAction.extras.get(key)) { + return false + } + } + } + return true + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt index a86515990fcb..d9c58c0d0d76 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt @@ -102,6 +102,17 @@ class MediaTimeoutLogger @Inject constructor( } ) + fun logStateCallback(key: String) = buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = key + }, + { + "dispatching state update for $key" + } + ) + fun logScheduleTimeout(key: String, playing: Boolean, resumption: Boolean) = buffer.log( TAG, LogLevel.DEBUG, diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java index fecc903326f5..4f598ff797d0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java @@ -49,9 +49,11 @@ public class ResumeMediaBrowser { private static final String TAG = "ResumeMediaBrowser"; private final Context mContext; @Nullable private final Callback mCallback; - private MediaBrowserFactory mBrowserFactory; + private final MediaBrowserFactory mBrowserFactory; + private final ResumeMediaBrowserLogger mLogger; + private final ComponentName mComponentName; + private MediaBrowser mMediaBrowser; - private ComponentName mComponentName; /** * Initialize a new media browser @@ -59,12 +61,17 @@ public class ResumeMediaBrowser { * @param callback used to report media items found * @param componentName Component name of the MediaBrowserService this browser will connect to */ - public ResumeMediaBrowser(Context context, @Nullable Callback callback, - ComponentName componentName, MediaBrowserFactory browserFactory) { + public ResumeMediaBrowser( + Context context, + @Nullable Callback callback, + ComponentName componentName, + MediaBrowserFactory browserFactory, + ResumeMediaBrowserLogger logger) { mContext = context; mCallback = callback; mComponentName = componentName; mBrowserFactory = browserFactory; + mLogger = logger; } /** @@ -76,7 +83,6 @@ public class ResumeMediaBrowser { * ResumeMediaBrowser#disconnect will be called automatically with this function. */ public void findRecentMedia() { - Log.d(TAG, "Connecting to " + mComponentName); disconnect(); Bundle rootHints = new Bundle(); rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true); @@ -84,6 +90,7 @@ public class ResumeMediaBrowser { mComponentName, mConnectionCallback, rootHints); + mLogger.logConnection(mComponentName, "findRecentMedia"); mMediaBrowser.connect(); } @@ -196,6 +203,7 @@ public class ResumeMediaBrowser { */ protected void disconnect() { if (mMediaBrowser != null) { + mLogger.logDisconnect(mComponentName); mMediaBrowser.disconnect(); } mMediaBrowser = null; @@ -251,6 +259,7 @@ public class ResumeMediaBrowser { disconnect(); } }, rootHints); + mLogger.logConnection(mComponentName, "restart"); mMediaBrowser.connect(); } @@ -296,6 +305,7 @@ public class ResumeMediaBrowser { mComponentName, mConnectionCallback, rootHints); + mLogger.logConnection(mComponentName, "testConnection"); mMediaBrowser.connect(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java index 2261aa5ac265..3d1380b6bd24 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java @@ -27,11 +27,14 @@ import javax.inject.Inject; public class ResumeMediaBrowserFactory { private final Context mContext; private final MediaBrowserFactory mBrowserFactory; + private final ResumeMediaBrowserLogger mLogger; @Inject - public ResumeMediaBrowserFactory(Context context, MediaBrowserFactory browserFactory) { + public ResumeMediaBrowserFactory( + Context context, MediaBrowserFactory browserFactory, ResumeMediaBrowserLogger logger) { mContext = context; mBrowserFactory = browserFactory; + mLogger = logger; } /** @@ -43,6 +46,6 @@ public class ResumeMediaBrowserFactory { */ public ResumeMediaBrowser create(ResumeMediaBrowser.Callback callback, ComponentName componentName) { - return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory); + return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory, mLogger); } } diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt new file mode 100644 index 000000000000..ccc5edc1123a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media + +import android.content.ComponentName +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.MediaBrowserLog +import javax.inject.Inject + +/** A logger for events in [ResumeMediaBrowser]. */ +@SysUISingleton +class ResumeMediaBrowserLogger @Inject constructor( + @MediaBrowserLog private val buffer: LogBuffer +) { + /** Logs that we've initiated a connection to a [android.media.browse.MediaBrowser]. */ + fun logConnection(componentName: ComponentName, reason: String) = buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = componentName.toShortString() + str2 = reason + }, + { "Connecting browser for component $str1 due to $str2" } + ) + + /** Logs that we've disconnected from a [android.media.browse.MediaBrowser]. */ + fun logDisconnect(componentName: ComponentName) = buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = componentName.toShortString() + }, + { "Disconnecting browser for component $str1" } + ) +} + +private const val TAG = "MediaBrowser" diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt index 930c5a8de125..50a96f601443 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt @@ -31,10 +31,6 @@ data class SmartspaceMediaData( */ val isActive: Boolean, /** - * Indicates if all the required data field is valid. - */ - val isValid: Boolean, - /** * Package name of the media recommendations' provider-app. */ val packageName: String, @@ -58,4 +54,19 @@ data class SmartspaceMediaData( * Instance ID for [MediaUiEventLogger] */ val instanceId: InstanceId -) +) { + /** + * Indicates if all the data is valid. + * + * TODO(b/230333302): Make MediaControlPanel more flexible so that we can display fewer than + * [NUM_REQUIRED_RECOMMENDATIONS]. + */ + fun isValid() = getValidRecommendations().size >= NUM_REQUIRED_RECOMMENDATIONS + + /** + * Returns the list of [recommendations] that have valid data. + */ + fun getValidRecommendations() = recommendations.filter { it.icon != null } +} + +const val NUM_REQUIRED_RECOMMENDATIONS = 3 diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java index 9b3b3ce6109f..dd4f1d6c9015 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java @@ -59,11 +59,14 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { private ImageView mBroadcastCodeEye; private Boolean mIsPasswordHide = true; private ImageView mBroadcastCodeEdit; - private Button mStopButton; + private AlertDialog mAlertDialog; + private TextView mBroadcastErrorMessage; static final int METADATA_BROADCAST_NAME = 0; static final int METADATA_BROADCAST_CODE = 1; + private static final int MAX_BROADCAST_INFO_UPDATE = 3; + MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar, BroadcastSender broadcastSender, MediaOutputController mediaOutputController) { super(context, broadcastSender, mediaOutputController); @@ -118,14 +121,18 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { return View.VISIBLE; } - // TODO(b/222674827): To get the information from BluetoothLeBroadcastMetadata(Broadcast code) - // and BluetoothLeAudioContentMetadata(Program info) when start Broadcast is successful. - private String getBroadcastMetaDataInfo(int metaData) { - switch (metaData) { + @Override + public void onStopButtonClick() { + mMediaOutputController.stopBluetoothLeBroadcast(); + dismiss(); + } + + private String getBroadcastMetadataInfo(int metadata) { + switch (metadata) { case METADATA_BROADCAST_NAME: - return ""; + return mMediaOutputController.getBroadcastName(); case METADATA_BROADCAST_CODE: - return ""; + return mMediaOutputController.getBroadcastCode(); default: return ""; } @@ -164,13 +171,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { launchBroadcastUpdatedDialog(true, mBroadcastCode.getText().toString()); }); - mBroadcastName.setText(getBroadcastMetaDataInfo(METADATA_BROADCAST_NAME)); - mBroadcastCode.setText(getBroadcastMetaDataInfo(METADATA_BROADCAST_CODE)); - - mStopButton = getDialogView().requireViewById(R.id.stop); - mStopButton.setOnClickListener(v -> { - stopBroadcast(); - }); + mBroadcastName.setText(getBroadcastMetadataInfo(METADATA_BROADCAST_NAME)); + mBroadcastCode.setText(getBroadcastMetadataInfo(METADATA_BROADCAST_CODE)); } private void inflateBroadcastInfoArea() { @@ -179,16 +181,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { } private void setQrCodeView() { - //get the MetaData, and convert to BT QR code format. - String broadcastMetaData = getBroadcastMetaData(); - if (broadcastMetaData.isEmpty()) { + //get the Metadata, and convert to BT QR code format. + String broadcastMetadata = getBroadcastMetadata(); + if (broadcastMetadata.isEmpty()) { //TDOD(b/226708424) Error handling for unable to generate the QR code bitmap return; } try { final int qrcodeSize = getContext().getResources().getDimensionPixelSize( R.dimen.media_output_qrcode_size); - final Bitmap bmp = QrCodeGenerator.encodeQrCode(broadcastMetaData, qrcodeSize); + final Bitmap bmp = QrCodeGenerator.encodeQrCode(broadcastMetadata, qrcodeSize); mBroadcastQrCodeView.setImageBitmap(bmp); } catch (WriterException e) { //TDOD(b/226708424) Error handling for unable to generate the QR code bitmap @@ -203,50 +205,87 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { mIsPasswordHide = !mIsPasswordHide; } - private void launchBroadcastUpdatedDialog(boolean isPassword, String editString) { + private void launchBroadcastUpdatedDialog(boolean isBroadcastCode, String editString) { final View layout = LayoutInflater.from(mContext).inflate( R.layout.media_output_broadcast_update_dialog, null); final EditText editText = layout.requireViewById(R.id.broadcast_edit_text); editText.setText(editString); - final AlertDialog alertDialog = new Builder(mContext) - .setTitle(isPassword ? R.string.media_output_broadcast_code + mBroadcastErrorMessage = layout.requireViewById(R.id.broadcast_error_message); + mAlertDialog = new Builder(mContext) + .setTitle(isBroadcastCode ? R.string.media_output_broadcast_code : R.string.media_output_broadcast_name) .setView(layout) .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(R.string.media_output_broadcast_dialog_save, (d, w) -> { - updateBroadcast(isPassword, editText.getText().toString()); + updateBroadcastInfo(isBroadcastCode, editText.getText().toString()); }) .create(); - alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - SystemUIDialog.setShowForAllUsers(alertDialog, true); - SystemUIDialog.registerDismissListener(alertDialog); - alertDialog.show(); + mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + SystemUIDialog.setShowForAllUsers(mAlertDialog, true); + SystemUIDialog.registerDismissListener(mAlertDialog); + mAlertDialog.show(); } - /** - * TODO(b/222674827): The method should be get the BluetoothLeBroadcastMetadata after - * starting the Broadcast session successfully. Then we will follow the BT QR code format - * that convert BluetoothLeBroadcastMetadata object to String format. - */ - private String getBroadcastMetaData() { - return "TEST"; + private String getBroadcastMetadata() { + return mMediaOutputController.getBroadcastMetadata(); } - /** - * TODO(b/222676140): These method are about the LE Audio Broadcast API. The framework APIS - * will be wrapped in SettingsLib. And the UI will be executed through it. - */ - private void updateBroadcast(boolean isPassword, String updatedString) { + private void updateBroadcastInfo(boolean isBroadcastCode, String updatedString) { + Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE); + if (positiveBtn != null) { + positiveBtn.setEnabled(false); + } + if (isBroadcastCode) { + handleBroadcastCodeUpdated(updatedString); + } else { + handleBroadcastNameUpdated(updatedString); + } } - /** - * TODO(b/222676140): These method are about the LE Audio Broadcast API. The framework APIS - * will be wrapped in SettingsLib. And the UI will be executed through it. - */ - private void stopBroadcast() { - dismiss(); + private void handleBroadcastNameUpdated(String name) { + // TODO(b/230473995) Add the retry mechanism and error handling when update fails + String currentName = mMediaOutputController.getBroadcastName(); + int retryCount = MAX_BROADCAST_INFO_UPDATE; + mMediaOutputController.setBroadcastName(name); + if (!mMediaOutputController.updateBluetoothLeBroadcast()) { + mMediaOutputController.setBroadcastName(currentName); + handleLeUpdateBroadcastFailed(retryCount); + } + } + + private void handleBroadcastCodeUpdated(String newPassword) { + // TODO(b/230473995) Add the retry mechanism and error handling when update fails + String currentPassword = mMediaOutputController.getBroadcastCode(); + int retryCount = MAX_BROADCAST_INFO_UPDATE; + if (!mMediaOutputController.stopBluetoothLeBroadcast()) { + mMediaOutputController.setBroadcastCode(currentPassword); + handleLeUpdateBroadcastFailed(retryCount); + return; + } + + mMediaOutputController.setBroadcastCode(newPassword); + if (!mMediaOutputController.startBluetoothLeBroadcast()) { + mMediaOutputController.setBroadcastCode(currentPassword); + handleLeUpdateBroadcastFailed(retryCount); + return; + } + + mAlertDialog.dismiss(); + } + + private void handleLeUpdateBroadcastFailed(int retryCount) { + final Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE); + mBroadcastErrorMessage.setVisibility(View.VISIBLE); + if (retryCount < MAX_BROADCAST_INFO_UPDATE) { + if (positiveBtn != null) { + positiveBtn.setEnabled(true); + } + mBroadcastErrorMessage.setText(R.string.media_output_broadcast_update_error); + } else { + mBroadcastErrorMessage.setText(R.string.media_output_broadcast_last_update_error); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt new file mode 100644 index 000000000000..31266b6dc8ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.dialog + +import android.content.Context +import android.media.session.MediaSessionManager +import android.view.View +import com.android.internal.logging.UiEventLogger +import com.android.settingslib.bluetooth.LocalBluetoothManager +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.broadcast.BroadcastSender +import com.android.systemui.media.nearby.NearbyMediaDevicesManager +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection +import java.util.Optional +import javax.inject.Inject + +/** + * Factory to create [MediaOutputBroadcastDialog] objects. + */ +class MediaOutputBroadcastDialogFactory @Inject constructor( + private val context: Context, + private val mediaSessionManager: MediaSessionManager, + private val lbm: LocalBluetoothManager?, + private val starter: ActivityStarter, + private val broadcastSender: BroadcastSender, + private val notifCollection: CommonNotifCollection, + private val uiEventLogger: UiEventLogger, + private val dialogLaunchAnimator: DialogLaunchAnimator, + private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager> +) { + var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null + + /** Creates a [MediaOutputBroadcastDialog] for the given package. */ + fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) { + // Dismiss the previous dialog, if any. + mediaOutputBroadcastDialog?.dismiss() + + val controller = MediaOutputController(context, packageName, + mediaSessionManager, lbm, starter, notifCollection, + dialogLaunchAnimator, nearbyMediaDevicesManagerOptional) + val dialog = + MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller) + mediaOutputBroadcastDialog = dialog + + // Show the dialog. + if (view != null) { + dialogLaunchAnimator.showFromView(dialog, view) + } else { + dialog.show() + } + } + + /** dismiss [MediaOutputBroadcastDialog] if exist. */ + fun dismiss() { + mediaOutputBroadcastDialog?.dismiss() + mediaOutputBroadcastDialog = null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 0fbec3baffa6..8723f4fd63bb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -63,7 +63,6 @@ import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.media.BluetoothMediaDevice; import com.android.settingslib.media.InfoMediaManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; @@ -79,6 +78,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.phone.SystemUIDialog; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -678,6 +678,56 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog); } + String getBroadcastName() { + LocalBluetoothLeBroadcast broadcast = + mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); + if (broadcast == null) { + Log.d(TAG, "getBroadcastName: LE Audio Broadcast is null"); + return ""; + } + return broadcast.getProgramInfo(); + } + + void setBroadcastName(String broadcastName) { + LocalBluetoothLeBroadcast broadcast = + mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); + if (broadcast == null) { + Log.d(TAG, "setBroadcastName: LE Audio Broadcast is null"); + return; + } + broadcast.setProgramInfo(broadcastName); + } + + String getBroadcastCode() { + LocalBluetoothLeBroadcast broadcast = + mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); + if (broadcast == null) { + Log.d(TAG, "getBroadcastCode: LE Audio Broadcast is null"); + return ""; + } + return new String(broadcast.getBroadcastCode(), StandardCharsets.UTF_8); + } + + void setBroadcastCode(String broadcastCode) { + LocalBluetoothLeBroadcast broadcast = + mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); + if (broadcast == null) { + Log.d(TAG, "setBroadcastCode: LE Audio Broadcast is null"); + return; + } + broadcast.setBroadcastCode(broadcastCode.getBytes(StandardCharsets.UTF_8)); + } + + String getBroadcastMetadata() { + LocalBluetoothLeBroadcast broadcast = + mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); + if (broadcast == null) { + Log.d(TAG, "getBroadcastMetadata: LE Audio Broadcast is null"); + return ""; + } + return broadcast.getLocalBluetoothLeBroadcastMetaData().convertToQrCodeString(); + } + boolean isActiveRemoteDevice(@NonNull MediaDevice device) { final List<String> features = device.getFeatures(); return (features.contains(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK) @@ -723,6 +773,17 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, return true; } + boolean updateBluetoothLeBroadcast() { + LocalBluetoothLeBroadcast broadcast = + mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); + if (broadcast == null) { + Log.d(TAG, "The broadcast profile is null"); + return false; + } + broadcast.updateBroadcast(getAppSourceName(), /*language*/ null); + return true; + } + void registerLeBroadcastServiceCallBack( @NonNull @CallbackExecutor Executor executor, @NonNull BluetoothLeBroadcast.Callback callback) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt index 7fb7d8b0eaa5..dd9d35bf2021 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt @@ -31,7 +31,8 @@ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) * BroadcastReceiver for handling media output intent */ class MediaOutputDialogReceiver @Inject constructor( - private val mediaOutputDialogFactory: MediaOutputDialogFactory + private val mediaOutputDialogFactory: MediaOutputDialogFactory, + private val mediaOutputBroadcastDialogFactory: MediaOutputBroadcastDialogFactory ) : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (TextUtils.equals(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG, @@ -43,6 +44,16 @@ class MediaOutputDialogReceiver @Inject constructor( } else if (DEBUG) { Log.e(TAG, "Unable to launch media output dialog. Package name is empty.") } + } else if (TextUtils.equals( + MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG, + intent.action)) { + val packageName: String? = + intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME) + if (!TextUtils.isEmpty(packageName)) { + mediaOutputBroadcastDialogFactory.create(packageName!!, false) + } else if (DEBUG) { + Log.e(TAG, "Unable to launch media output broadcast dialog. Package name is empty.") + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java index 0fe909552cb1..abebf3e80b21 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java @@ -61,6 +61,7 @@ public class QSFgsManagerFooter implements View.OnClickListener, private final View mNumberContainer; private final TextView mNumberView; private final ImageView mDotView; + private final ImageView mCollapsedDotView; @Nullable private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener; @@ -75,6 +76,7 @@ public class QSFgsManagerFooter implements View.OnClickListener, mNumberContainer = mRootView.findViewById(R.id.fgs_number_container); mNumberView = mRootView.findViewById(R.id.fgs_number); mDotView = mRootView.findViewById(R.id.fgs_new); + mCollapsedDotView = mRootView.findViewById(R.id.fgs_collapsed_new); mContext = rootView.getContext(); mMainExecutor = mainExecutor; mExecutor = executor; @@ -147,8 +149,10 @@ public class QSFgsManagerFooter implements View.OnClickListener, if (mFgsManagerController.shouldUpdateFooterVisibility()) { mRootView.setVisibility(mNumPackages > 0 && mFgsManagerController.isAvailable() ? View.VISIBLE : View.GONE); - mDotView.setVisibility( - mFgsManagerController.getChangesSinceDialog() ? View.VISIBLE : View.GONE); + int dotVis = + mFgsManagerController.getChangesSinceDialog() ? View.VISIBLE : View.GONE; + mDotView.setVisibility(dotVis); + mCollapsedDotView.setVisibility(dotVis); if (mVisibilityChangedListener != null) { mVisibilityChangedListener.onVisibilityChanged(mRootView.getVisibility()); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 9ef90ecf50a7..311ee56477de 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -17,6 +17,7 @@ import com.android.systemui.R; import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.QSPanelControllerBase.TileRecord; import com.android.systemui.qs.tileimpl.HeightOverrideable; +import com.android.systemui.qs.tileimpl.QSTileViewImplKt; import java.util.ArrayList; @@ -242,7 +243,12 @@ public class TileLayout extends ViewGroup implements QSTileLayout { record.tileView.setLeftTopRightBottom(left, top, right, bottom); } record.tileView.setPosition(i); - mLastTileBottom = bottom; + if (forLayout) { + mLastTileBottom = record.tileView.getBottom(); + } else { + float scale = QSTileViewImplKt.constrainSquishiness(mSquishinessFraction); + mLastTileBottom = top + (int) (record.tileView.getMeasuredHeight() * scale); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 72dad0608a3a..59164dea9c45 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -262,7 +262,7 @@ open class QSTileViewImpl @JvmOverloads constructor( } // Limit how much we affect the height, so we don't have rounding artifacts when the tile // is too short. - val constrainedSquishiness = 0.1f + squishinessFraction * 0.9f + val constrainedSquishiness = constrainSquishiness(squishinessFraction) bottom = top + (actualHeight * constrainedSquishiness).toInt() scrollY = (actualHeight - height) / 2 } @@ -678,6 +678,10 @@ internal object SubtitleArrayMapping { } } +fun constrainSquishiness(squish: Float): Float { + return 0.1f + squish * 0.9f +} + private fun colorValuesHolder(name: String, vararg values: Int): PropertyValuesHolder { return PropertyValuesHolder.ofInt(name, *values).apply { setEvaluator(ArgbEvaluator.getInstance()) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 5585cde528fa..aa80b730d24f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -23,7 +23,7 @@ import static android.inputmethodservice.InputMethodService.IME_INVISIBLE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; -import static com.android.systemui.statusbar.phone.CentralSurfaces.ONLY_CORE_APPS; +import static com.android.systemui.statusbar.phone.CentralSurfacesImpl.ONLY_CORE_APPS; import android.annotation.Nullable; import android.app.ITransientNotificationCallback; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 270bdc785178..0a616c095551 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -17,6 +17,7 @@ import android.util.MathUtils.lerp import android.view.View import com.android.systemui.animation.Interpolators import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold +import com.android.systemui.util.getColorWithAlpha import java.util.function.Consumer /** @@ -367,7 +368,7 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, } if (startColorAlpha > 0f) { - canvas.drawColor(updateColorAlpha(revealGradientEndColor, startColorAlpha)) + canvas.drawColor(getColorWithAlpha(revealGradientEndColor, startColorAlpha)) } with(shaderGradientMatrix) { @@ -383,15 +384,7 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, private fun setPaintColorFilter() { gradientPaint.colorFilter = PorterDuffColorFilter( - updateColorAlpha(revealGradientEndColor, revealGradientEndColorAlpha), + getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha), PorterDuff.Mode.MULTIPLY) } - - private fun updateColorAlpha(color: Int, alpha: Float): Int = - Color.argb( - (alpha * 255).toInt(), - Color.red(color), - Color.green(color), - Color.blue(color) - ) }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index afce945a5bc5..734bc48093b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -39,7 +39,6 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; -import com.android.systemui.statusbar.notification.row.NotificationBackgroundView; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; @@ -411,7 +410,7 @@ public class NotificationShelf extends ActivatableNotificationView implements setBackgroundTop(backgroundTop); setFirstElementRoundness(firstElementRoundness); mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex()); - mShelfIcons.calculateIconTranslations(); + mShelfIcons.calculateIconXTranslations(); mShelfIcons.applyIconStates(); for (int i = 0; i < mHostLayoutController.getChildCount(); i++) { View child = mHostLayoutController.getChildAt(i); @@ -636,7 +635,7 @@ public class NotificationShelf extends ActivatableNotificationView implements float viewEnd = viewStart + fullHeight; float fullTransitionAmount = 0.0f; float iconTransitionAmount = 0.0f; - float shelfStart = getTranslationY(); + float shelfStart = getTranslationY() - mPaddingBetweenElements; if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) { // TODO(b/172289889) handle icon placement for notification that is clipped by the shelf if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index dea429f6c617..d49e1e6acc23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -33,7 +33,6 @@ import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.NetworkScoreManager; import android.net.wifi.ScanResult; import android.net.wifi.WifiManager; import android.os.AsyncTask; @@ -225,10 +224,10 @@ public class NetworkControllerImpl extends BroadcastReceiver TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager, @Nullable WifiManager wifiManager, - NetworkScoreManager networkScoreManager, AccessPointControllerImpl accessPointController, DemoModeController demoModeController, CarrierConfigTracker carrierConfigTracker, + WifiStatusTrackerFactory trackerFactory, @Main Handler handler, InternetDialogFactory internetDialogFactory, FeatureFlags featureFlags, @@ -237,7 +236,6 @@ public class NetworkControllerImpl extends BroadcastReceiver telephonyManager, telephonyListenerManager, wifiManager, - networkScoreManager, subscriptionManager, Config.readConfig(context), bgLooper, @@ -250,6 +248,7 @@ public class NetworkControllerImpl extends BroadcastReceiver broadcastDispatcher, demoModeController, carrierConfigTracker, + trackerFactory, handler, featureFlags, dumpManager); @@ -262,8 +261,9 @@ public class NetworkControllerImpl extends BroadcastReceiver TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager, WifiManager wifiManager, - NetworkScoreManager networkScoreManager, - SubscriptionManager subManager, Config config, Looper bgLooper, + SubscriptionManager subManager, + Config config, + Looper bgLooper, Executor bgExecutor, CallbackHandler callbackHandler, AccessPointControllerImpl accessPointController, @@ -273,6 +273,7 @@ public class NetworkControllerImpl extends BroadcastReceiver BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController, CarrierConfigTracker carrierConfigTracker, + WifiStatusTrackerFactory trackerFactory, @Main Handler handler, FeatureFlags featureFlags, DumpManager dumpManager @@ -315,9 +316,10 @@ public class NetworkControllerImpl extends BroadcastReceiver notifyControllersMobileDataChanged(); } }); + mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature, - mCallbackHandler, this, mWifiManager, mConnectivityManager, networkScoreManager, - mMainHandler, mReceiverHandler); + mCallbackHandler, this, mWifiManager, trackerFactory, + mReceiverHandler); mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java index e2806a39130f..7e8f04e4bc23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java @@ -50,7 +50,7 @@ public abstract class SignalController<T extends ConnectivityState, I extends Ic protected final T mLastState; protected final int mTransportType; protected final Context mContext; - // The owner of the SignalController (i.e. NetworkController will maintain the following + // The owner of the SignalController (i.e. NetworkController) will maintain the following // lists and call notifyListeners whenever the list has changed to ensure everyone // is aware of current state. protected final NetworkControllerImpl mNetworkController; @@ -103,7 +103,7 @@ public abstract class SignalController<T extends ConnectivityState, I extends Ic * Determines if the state of this signal controller has changed and * needs to trigger callbacks related to it. */ - public boolean isDirty() { + boolean isDirty() { if (!mLastState.equals(mCurrentState)) { if (DEBUG) { Log.d(mTag, "Change in state from: " + mLastState + "\n" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java index a4589c8dd6d3..87cdb17245f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java @@ -21,14 +21,13 @@ import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OU import android.content.Context; import android.content.Intent; -import android.net.ConnectivityManager; import android.net.NetworkCapabilities; -import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; import android.os.Handler; import android.text.Html; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import com.android.settingslib.SignalIcon.IconGroup; import com.android.settingslib.SignalIcon.MobileIconGroup; import com.android.settingslib.graph.SignalDrawable; @@ -36,8 +35,6 @@ import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.wifi.WifiStatusTracker; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.util.Assert; import java.io.PrintWriter; @@ -49,21 +46,21 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> private final MobileIconGroup mCarrierMergedWifiIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI; private final WifiManager mWifiManager; + private final Handler mBgHandler; + public WifiSignalController( Context context, boolean hasMobileDataFeature, CallbackHandler callbackHandler, NetworkControllerImpl networkController, WifiManager wifiManager, - ConnectivityManager connectivityManager, - NetworkScoreManager networkScoreManager, - @Main Handler handler, - @Background Handler backgroundHandler) { + WifiStatusTrackerFactory trackerFactory, + @Background Handler bgHandler) { super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI, callbackHandler, networkController); + mBgHandler = bgHandler; mWifiManager = wifiManager; - mWifiTracker = new WifiStatusTracker(mContext, wifiManager, networkScoreManager, - connectivityManager, this::handleStatusUpdated, handler, backgroundHandler); + mWifiTracker = trackerFactory.createTracker(this::handleStatusUpdated, bgHandler); mWifiTracker.setListening(true); mHasMobileDataFeature = hasMobileDataFeature; if (wifiManager != null) { @@ -181,33 +178,51 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> * Fetches wifi initial state replacing the initial sticky broadcast. */ public void fetchInitialState() { - mWifiTracker.fetchInitialState(); - copyWifiStates(); - notifyListenersIfNecessary(); + doInBackground(() -> { + mWifiTracker.fetchInitialState(); + copyWifiStates(); + notifyListenersIfNecessary(); + }); } /** * Extract wifi state directly from broadcasts about changes in wifi state. */ - public void handleBroadcast(Intent intent) { - mWifiTracker.handleBroadcast(intent); - copyWifiStates(); - notifyListenersIfNecessary(); + void handleBroadcast(Intent intent) { + doInBackground(() -> { + mWifiTracker.handleBroadcast(intent); + copyWifiStates(); + notifyListenersIfNecessary(); + }); } private void handleStatusUpdated() { - Assert.isMainThread(); - copyWifiStates(); - notifyListenersIfNecessary(); + // The WifiStatusTracker callback comes in on the main thread, but the rest of our data + // access happens on the bgHandler + doInBackground(() -> { + copyWifiStates(); + notifyListenersIfNecessary(); + }); + } + + private void doInBackground(Runnable action) { + if (Thread.currentThread() != mBgHandler.getLooper().getThread()) { + mBgHandler.post(action); + } else { + action.run(); + } } private void copyWifiStates() { + // Data access should only happen on our bg thread + Preconditions.checkState(mBgHandler.getLooper().isCurrentThread()); + mCurrentState.enabled = mWifiTracker.enabled; mCurrentState.isDefault = mWifiTracker.isDefaultNetwork; mCurrentState.connected = mWifiTracker.connected; mCurrentState.ssid = mWifiTracker.ssid; mCurrentState.rssi = mWifiTracker.rssi; - notifyWifiLevelChangeIfNecessary(mWifiTracker.level); + boolean levelChanged = mCurrentState.level != mWifiTracker.level; mCurrentState.level = mWifiTracker.level; mCurrentState.statusLabel = mWifiTracker.statusLabel; mCurrentState.isCarrierMerged = mWifiTracker.isCarrierMerged; @@ -215,11 +230,9 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> mCurrentState.iconGroup = mCurrentState.isCarrierMerged ? mCarrierMergedWifiIconGroup : mUnmergedWifiIconGroup; - } - void notifyWifiLevelChangeIfNecessary(int level) { - if (level != mCurrentState.level) { - mNetworkController.notifyWifiLevelChange(level); + if (levelChanged) { + mNetworkController.notifyWifiLevelChange(mCurrentState.level); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiStatusTrackerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiStatusTrackerFactory.kt new file mode 100644 index 000000000000..9dc17d0f50de --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiStatusTrackerFactory.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.connectivity + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkScoreManager +import android.net.wifi.WifiManager +import android.os.Handler + +import com.android.settingslib.wifi.WifiStatusTracker +import com.android.systemui.dagger.qualifiers.Main + +import javax.inject.Inject + +/** + * Factory class for [WifiStatusTracker] which lives in SettingsLib (and thus doesn't use Dagger). + * This enables the constructors for NetworkControllerImpl and WifiSignalController to be slightly + * nicer. + */ +internal class WifiStatusTrackerFactory @Inject constructor( + private val mContext: Context, + private val mWifiManager: WifiManager?, + private val mNetworkScoreManager: NetworkScoreManager, + private val mConnectivityManager: ConnectivityManager, + @Main private val mMainHandler: Handler +) { + fun createTracker(callback: Runnable?, bgHandler: Handler?): WifiStatusTracker { + return WifiStatusTracker(mContext, + mWifiManager, + mNetworkScoreManager, + mConnectivityManager, + callback, + mMainHandler, + bgHandler) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 83290af24f2a..29411e63b163 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -61,6 +61,7 @@ import com.android.systemui.statusbar.notification.collection.legacy.VisualStabi import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.CentralSurfacesImpl; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl; @@ -87,7 +88,7 @@ import dagger.Module; import dagger.Provides; /** - * This module provides instances needed to construct {@link CentralSurfaces}. These are moved to + * This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to * this separate from {@link CentralSurfacesModule} module so that components that wish to build * their own version of CentralSurfaces can include just dependencies, without injecting * CentralSurfaces itself. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartCentralSurfacesModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartCentralSurfacesModule.kt index fe55dea7333a..e84d31dd6a0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartCentralSurfacesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartCentralSurfacesModule.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.dagger import com.android.systemui.CoreStartable import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.statusbar.phone.CentralSurfacesImpl import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -29,5 +30,5 @@ interface StartCentralSurfacesModule { @Binds @IntoMap @ClassKey(CentralSurfaces::class) - abstract fun bindsCentralSurfaces(centralSurfaces: CentralSurfaces): CoreStartable + abstract fun bindsCentralSurfaces(centralSurfaces: CentralSurfacesImpl): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index a4e2d5ec0829..a35bced819c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -28,7 +28,8 @@ import android.database.ContentObserver import android.net.Uri import android.os.Handler import android.os.UserHandle -import android.provider.Settings +import android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS +import android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS import android.util.Log import android.view.View import android.view.ViewGroup @@ -85,6 +86,7 @@ class LockscreenSmartspaceController @Inject constructor( // Smartspace can be used on multiple displays, such as when the user casts their screen private var smartspaceViews = mutableSetOf<SmartspaceView>() + private var showNotifications = false private var showSensitiveContentForCurrentUser = false private var showSensitiveContentForManagedUser = false private var managedUserHandle: UserHandle? = null @@ -233,7 +235,13 @@ class LockscreenSmartspaceController @Inject constructor( deviceProvisionedController.removeCallback(deviceProvisionedListener) userTracker.addCallback(userTrackerCallback, uiExecutor) contentResolver.registerContentObserver( - secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), + secureSettings.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), + true, + settingsObserver, + UserHandle.USER_ALL + ) + contentResolver.registerContentObserver( + secureSettings.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS), true, settingsObserver, UserHandle.USER_ALL @@ -286,6 +294,9 @@ class LockscreenSmartspaceController @Inject constructor( } private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean { + if (!showNotifications) { + return t.getFeatureType() == SmartspaceTarget.FEATURE_WEATHER + } return when (t.userHandle) { userTracker.userHandle -> { !t.isSensitive || showSensitiveContentForCurrentUser @@ -310,16 +321,26 @@ class LockscreenSmartspaceController @Inject constructor( } private fun reloadSmartspace() { - val setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS - - showSensitiveContentForCurrentUser = - secureSettings.getIntForUser(setting, 0, userTracker.userId) == 1 + showNotifications = secureSettings.getIntForUser( + LOCK_SCREEN_SHOW_NOTIFICATIONS, + 0, + userTracker.userId + ) == 1 + + showSensitiveContentForCurrentUser = secureSettings.getIntForUser( + LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, + 0, + userTracker.userId + ) == 1 managedUserHandle = getWorkProfileUser() val managedId = managedUserHandle?.identifier if (managedId != null) { - showSensitiveContentForManagedUser = - secureSettings.getIntForUser(setting, 0, managedId) == 1 + showSensitiveContentForManagedUser = secureSettings.getIntForUser( + LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, + 0, + managedId + ) == 1 } session?.requestSmartspaceUpdate() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 51af9559eda2..1b5e52d7f8fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -709,8 +709,8 @@ public class ShadeListBuilder implements Dumpable { new ArraySet<>(groupsWithChildrenLostToStability); // Any group which lost a child to filtering or promotion is exempt from having its summary // promoted when it has no attached children. - getGroupsWithChildrenLostToFiltering(groupsExemptFromSummaryPromotion); - getGroupsWithChildrenLostToPromotion(shadeList, groupsExemptFromSummaryPromotion); + addGroupsWithChildrenLostToFiltering(groupsExemptFromSummaryPromotion); + addGroupsWithChildrenLostToPromotion(shadeList, groupsExemptFromSummaryPromotion); // Iterate backwards, so that we can remove elements without affecting indices of // yet-to-be-accessed entries. @@ -749,7 +749,7 @@ public class ShadeListBuilder implements Dumpable { continue; } if (group.wasAttachedInPreviousPass() - && !getStabilityManager().isGroupChangeAllowed(group.getSummary())) { + && !getStabilityManager().isGroupPruneAllowed(group)) { checkState(!children.isEmpty(), "empty group should have been pruned"); // This group was previously attached and group changes aren't // allowed; keep it around until group changes are allowed again. @@ -865,7 +865,7 @@ public class ShadeListBuilder implements Dumpable { * * These groups will be exempt from appearing without any children. */ - private void getGroupsWithChildrenLostToPromotion(List<ListEntry> shadeList, Set<String> out) { + private void addGroupsWithChildrenLostToPromotion(List<ListEntry> shadeList, Set<String> out) { for (int i = 0; i < shadeList.size(); i++) { final ListEntry tle = shadeList.get(i); if (tle.getAttachState().getPromoter() != null) { @@ -882,13 +882,13 @@ public class ShadeListBuilder implements Dumpable { * * These groups will be exempt from appearing without any children. */ - private void getGroupsWithChildrenLostToFiltering(Set<String> out) { + private void addGroupsWithChildrenLostToFiltering(Set<String> out) { for (ListEntry tle : mAllEntries) { StatusBarNotification sbn = tle.getRepresentativeEntry().getSbn(); if (sbn.isGroup() && !sbn.getNotification().isGroupSummary() && tle.getAttachState().getExcludingFilter() != null) { - out.add(sbn.getGroup()); + out.add(sbn.getGroupKey()); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java index 085ce859de87..d7bd95c0949e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java @@ -27,6 +27,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -139,6 +140,13 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable, } @Override + public boolean isGroupPruneAllowed(@NonNull GroupEntry entry) { + final boolean isGroupPruneAllowedForEntry = mReorderingAllowed; + mIsSuppressingGroupChange |= !isGroupPruneAllowedForEntry; + return isGroupPruneAllowedForEntry; + } + + @Override public boolean isSectionChangeAllowed(@NonNull NotificationEntry entry) { final boolean isSectionChangeAllowedForEntry = mReorderingAllowed diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt index 446449864606..58bbca822164 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable +import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -52,6 +53,14 @@ abstract class NotifStabilityManager protected constructor(name: String) : abstract fun isGroupChangeAllowed(entry: NotificationEntry): Boolean /** + * Returns whether this notification group can be pruned for not having enough children. + * Per iteration of the notification pipeline, locally stores this information until the next + * run of the pipeline. When this method returns false, it's expected that a group prune for + * this entry is being suppressed. + */ + abstract fun isGroupPruneAllowed(entry: GroupEntry): Boolean + + /** * Returns whether this notification entry can currently change sections. * Per iteration of the notification pipeline, locally stores this information until the next * run of the pipeline. When this method returns false, it's expected that a section change is @@ -89,6 +98,7 @@ object DefaultNotifStabilityManager : NotifStabilityManager("DefaultNotifStabili override fun isPipelineRunAllowed(): Boolean = true override fun onBeginRun() {} override fun isGroupChangeAllowed(entry: NotificationEntry): Boolean = true + override fun isGroupPruneAllowed(entry: GroupEntry): Boolean = true override fun isSectionChangeAllowed(entry: NotificationEntry): Boolean = true override fun isEntryReorderingAllowed(entry: ListEntry): Boolean = true override fun isEveryChangeAllowed(): Boolean = true diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt index 032e6784ae08..386e2d31380c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.render import android.annotation.MainThread import android.view.View +import com.android.systemui.util.kotlin.transform import com.android.systemui.util.traceSection /** @@ -40,6 +41,7 @@ class ShadeViewDiffer( ) { private val rootNode = ShadeNode(rootController) private val nodes = mutableMapOf(rootController to rootNode) + private val views = mutableMapOf<View, ShadeNode>() /** * Adds and removes views from the root (and its children) until their structure matches the @@ -64,25 +66,26 @@ class ShadeViewDiffer( * * For debugging purposes. */ - fun getViewLabel(view: View): String = - nodes.values.firstOrNull { node -> node.view === view }?.label ?: view.toString() - - private fun detachChildren(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) { - val views = nodes.values.asSequence().map { node -> node.view to node }.toMap() - fun detachRecursively(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) { - val parentSpec = specMap[parentNode.controller] - for (i in parentNode.getChildCount() - 1 downTo 0) { - val childView = parentNode.getChildAt(i) - views[childView]?.let { childNode -> - val childSpec = specMap[childNode.controller] - maybeDetachChild(parentNode, parentSpec, childNode, childSpec) - if (childNode.controller.getChildCount() > 0) { - detachRecursively(childNode, specMap) - } + fun getViewLabel(view: View): String = views[view]?.label ?: view.toString() + + private fun detachChildren( + parentNode: ShadeNode, + specMap: Map<NodeController, NodeSpec> + ) { + val parentSpec = specMap[parentNode.controller] + + for (i in parentNode.getChildCount() - 1 downTo 0) { + val childView = parentNode.getChildAt(i) + views[childView]?.let { childNode -> + val childSpec = specMap[childNode.controller] + + maybeDetachChild(parentNode, parentSpec, childNode, childSpec) + + if (childNode.controller.getChildCount() > 0) { + detachChildren(childNode, specMap) } } } - detachRecursively(parentNode, specMap) } private fun maybeDetachChild( @@ -91,13 +94,14 @@ class ShadeViewDiffer( childNode: ShadeNode, childSpec: NodeSpec? ) { - val newParentNode = childSpec?.parent?.let { getNode(it) } + val newParentNode = transform(childSpec?.parent) { getNode(it) } if (newParentNode != parentNode) { val childCompletelyRemoved = newParentNode == null if (childCompletelyRemoved) { nodes.remove(childNode.controller) + views.remove(childNode.controller.view) } logger.logDetachingChild( @@ -111,7 +115,10 @@ class ShadeViewDiffer( } } - private fun attachChildren(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) { + private fun attachChildren( + parentNode: ShadeNode, + specMap: Map<NodeController, NodeSpec> + ) { val parentSpec = checkNotNull(specMap[parentNode.controller]) for ((index, childSpec) in parentSpec.children.withIndex()) { @@ -153,6 +160,7 @@ class ShadeViewDiffer( if (node == null) { node = ShadeNode(spec.controller) nodes[node.controller] = node + views[node.view] = node } return node } @@ -186,9 +194,10 @@ class ShadeViewDiffer( private class DuplicateNodeException(message: String) : RuntimeException(message) -private class ShadeNode(val controller: NodeController) { - val view: View - get() = controller.view +private class ShadeNode( + val controller: NodeController +) { + val view = controller.view var parent: ShadeNode? = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index c2750c2d2a6f..2493ccbe5a48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -117,6 +117,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; +import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -178,6 +179,7 @@ public class NotificationStackScrollLayoutController { private final CentralSurfaces mCentralSurfaces; private final SectionHeaderController mSilentHeaderController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; + private final ShadeTransitionController mShadeTransitionController; private final InteractionJankMonitor mJankMonitor; private final NotificationStackSizeCalculator mNotificationStackSizeCalculator; private final StackStateLogger mStackStateLogger; @@ -647,6 +649,7 @@ public class NotificationStackScrollLayoutController { NotifCollection notifCollection, NotificationEntryManager notificationEntryManager, LockscreenShadeTransitionController lockscreenShadeTransitionController, + ShadeTransitionController shadeTransitionController, IStatusBarService iStatusBarService, UiEventLogger uiEventLogger, LayoutInflater layoutInflater, @@ -675,6 +678,7 @@ public class NotificationStackScrollLayoutController { mLockscreenUserManager = lockscreenUserManager; mMetricsLogger = metricsLogger; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; + mShadeTransitionController = shadeTransitionController; mFalsingCollector = falsingCollector; mFalsingManager = falsingManager; mResources = resources; @@ -769,6 +773,7 @@ public class NotificationStackScrollLayoutController { mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming); mLockscreenShadeTransitionController.setStackScroller(this); + mShadeTransitionController.setNotificationStackScrollLayoutController(this); mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index 2b11f693da1d..9ea36d540f4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -207,9 +207,7 @@ constructor( visibleIndex: Int ): Float { var height = stack.calculateGapHeight(previous, current, visibleIndex) - if (visibleIndex != 0) { - height += dividerHeight - } + height += dividerHeight return height } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 22242b8fb4b4..2b79986662f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -314,7 +314,8 @@ public class StackScrollAlgorithm { if (ambientState.getShelf() != null) { final float shelfStart = ambientState.getStackEndHeight() - - ambientState.getShelf().getIntrinsicHeight(); + - ambientState.getShelf().getIntrinsicHeight() + - mPaddingBetweenElements; if (currentY >= shelfStart && !(view instanceof FooterView) && state.firstViewInShelf == null) { @@ -507,8 +508,9 @@ public class StackScrollAlgorithm { || bypassPulseNotExpanding ? ambientState.getInnerHeight() : (int) ambientState.getStackHeight(); - final int shelfStart = - stackBottom - ambientState.getShelf().getIntrinsicHeight(); + final int shelfStart = stackBottom + - ambientState.getShelf().getIntrinsicHeight() + - mPaddingBetweenElements; viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart); if (viewState.yTranslation >= shelfStart) { viewState.hidden = !view.isExpandAnimationRunning() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 83017c41629c..9c6ba3af5154 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,4528 +16,570 @@ package com.android.systemui.statusbar.phone; -import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; -import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; -import static android.app.StatusBarManager.WindowVisibleState; -import static android.app.StatusBarManager.windowStateToString; -import static android.view.InsetsState.ITYPE_STATUS_BAR; -import static android.view.InsetsState.containsType; -import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; -import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS; -import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS; - -import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; -import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; -import static androidx.lifecycle.Lifecycle.State.RESUMED; - -import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; -import static com.android.systemui.charging.WirelessChargingLayout.UNKNOWN_BATTERY_LEVEL; -import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; -import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; import android.annotation.Nullable; -import android.app.ActivityManager; import android.app.ActivityOptions; -import android.app.ActivityTaskManager; -import android.app.IWallpaperManager; -import android.app.KeyguardManager; -import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.StatusBarManager; -import android.app.TaskInfo; -import android.app.TaskStackBuilder; -import android.app.UiModeManager; -import android.app.WallpaperInfo; -import android.app.WallpaperManager; -import android.app.admin.DevicePolicyManager; -import android.content.BroadcastReceiver; -import android.content.ComponentCallbacks2; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.IPackageManager; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; -import android.content.res.Configuration; -import android.graphics.Point; -import android.graphics.PointF; -import android.hardware.devicestate.DeviceStateManager; -import android.metrics.LogMaker; -import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.os.PowerManager; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.os.Trace; import android.os.UserHandle; -import android.provider.Settings; -import android.service.dreams.DreamService; -import android.service.dreams.IDreamManager; import android.service.notification.StatusBarNotification; -import android.text.TextUtils; -import android.util.ArraySet; -import android.util.DisplayMetrics; -import android.util.EventLog; -import android.util.IndentingPrintWriter; -import android.util.Log; -import android.util.MathUtils; -import android.util.Slog; -import android.view.Display; -import android.view.IRemoteAnimationRunner; -import android.view.IWindowManager; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.RemoteAnimationAdapter; -import android.view.ThreadedRenderer; import android.view.View; import android.view.ViewGroup; -import android.view.WindowInsetsController.Appearance; -import android.view.WindowManager; -import android.view.WindowManagerGlobal; -import android.view.accessibility.AccessibilityManager; -import android.widget.DateTimeView; import android.window.SplashScreen; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.LifecycleRegistry; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.colorextraction.ColorExtractor; -import com.android.internal.jank.InteractionJankMonitor; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEvent; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.UiEventLoggerImpl; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.RegisterStatusBarResult; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.KeyguardUpdateMonitorCallback; -import com.android.keyguard.ViewMediatorCallback; -import com.android.systemui.ActivityIntentHelper; -import com.android.systemui.AutoReinflateContainer; -import com.android.systemui.CoreStartable; -import com.android.systemui.DejankUtils; -import com.android.systemui.EventLogTags; -import com.android.systemui.InitController; -import com.android.systemui.Prefs; -import com.android.systemui.R; -import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; +import com.android.systemui.Dumpable; import com.android.systemui.animation.ActivityLaunchAnimator; -import com.android.systemui.animation.DelegateLaunchAnimatorController; import com.android.systemui.animation.RemoteTransitionAdapter; -import com.android.systemui.assist.AssistManager; -import com.android.systemui.biometrics.AuthRippleController; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.camera.CameraIntents; -import com.android.systemui.charging.WirelessChargingAnimation; -import com.android.systemui.classifier.FalsingCollector; -import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dagger.qualifiers.UiBackground; -import com.android.systemui.demomode.DemoMode; -import com.android.systemui.demomode.DemoModeController; -import com.android.systemui.dreams.DreamOverlayStateController; -import com.android.systemui.emergency.EmergencyGesture; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; -import com.android.systemui.fragments.ExtensionFragmentListener; -import com.android.systemui.fragments.FragmentHostManager; -import com.android.systemui.fragments.FragmentService; -import com.android.systemui.keyguard.KeyguardService; -import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.keyguard.ScreenLifecycle; -import com.android.systemui.keyguard.WakefulnessLifecycle; -import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.OverlayPlugin; -import com.android.systemui.plugins.PluginDependencyProvider; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.qs.QS; -import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.qs.QSFragment; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.qs.QSPanelController; -import com.android.systemui.recents.ScreenPinningRequest; -import com.android.systemui.scrim.ScrimView; -import com.android.systemui.settings.brightness.BrightnessSliderController; -import com.android.systemui.shared.plugins.PluginManager; -import com.android.systemui.statusbar.AutoHideUiElement; -import com.android.systemui.statusbar.BackDropView; -import com.android.systemui.statusbar.CircleReveal; -import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.GestureRecorder; -import com.android.systemui.statusbar.KeyboardShortcuts; -import com.android.systemui.statusbar.KeyguardIndicationController; -import com.android.systemui.statusbar.LiftReveal; import com.android.systemui.statusbar.LightRevealScrim; -import com.android.systemui.statusbar.LockscreenShadeTransitionController; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationShadeDepthController; -import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.NotificationShelfController; -import com.android.systemui.statusbar.NotificationViewHierarchyManager; -import com.android.systemui.statusbar.PowerButtonReveal; -import com.android.systemui.statusbar.PulseExpansionHandler; -import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.SysuiStatusBarStateController; -import com.android.systemui.statusbar.charging.WiredChargingRippleController; -import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.core.StatusBarInitializer; -import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotifPipelineFlags; -import com.android.systemui.statusbar.notification.NotificationActivityStarter; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; -import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; -import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; -import com.android.systemui.statusbar.notification.init.NotificationsController; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.stack.NotificationListContainer; -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; -import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule; -import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; -import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent; -import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; -import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.BrightnessMirrorController; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; -import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.UserInfoControllerImpl; -import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.statusbar.window.StatusBarWindowController; -import com.android.systemui.statusbar.window.StatusBarWindowStateController; -import com.android.systemui.util.DumpUtilsKt; -import com.android.systemui.util.WallpaperController; -import com.android.systemui.util.concurrency.DelayableExecutor; -import com.android.systemui.util.concurrency.MessageRouter; -import com.android.systemui.volume.VolumeComponent; -import com.android.systemui.wmshell.BubblesManager; -import com.android.wm.shell.bubbles.Bubbles; -import com.android.wm.shell.startingsurface.SplashscreenContentDrawer; -import com.android.wm.shell.startingsurface.StartingSurface; import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.Executor; - -import javax.inject.Named; - -import dagger.Lazy; - -/** - * A class handling initialization and coordination between some of the key central surfaces in - * System UI: The notification shade, the keyguard (lockscreen), and the status bar. - * - * This class is not our ideal architecture because it doesn't enforce much isolation between these - * three mostly disparate surfaces. In an ideal world, this class would not exist. Instead, we would - * break it up into three modules -- one for each of those three surfaces -- and we would define any - * APIs that are needed for these surfaces to communicate with each other when necessary. - * - * <b>If at all possible, please avoid adding additional code to this monstrous class! Our goal is - * to break up this class into many small classes, and any code added here will slow down that goal. - * </b> - */ -public class CentralSurfaces extends CoreStartable implements - ActivityStarter, - LifecycleOwner { - public static final boolean MULTIUSER_DEBUG = false; - - protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027; +public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwner { + boolean MULTIUSER_DEBUG = false; // Should match the values in PhoneWindowManager - public static final String SYSTEM_DIALOG_REASON_KEY = "reason"; - public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; - public static final String SYSTEM_DIALOG_REASON_DREAM = "dream"; - static public final String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot"; - - private static final String BANNER_ACTION_CANCEL = - "com.android.systemui.statusbar.banner_action_cancel"; - private static final String BANNER_ACTION_SETUP = - "com.android.systemui.statusbar.banner_action_setup"; - public static final String TAG = "CentralSurfaces"; - public static final boolean DEBUG = false; - public static final boolean SPEW = false; - public static final boolean DUMPTRUCK = true; // extra dumpsys info - public static final boolean DEBUG_GESTURES = false; - public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false; - public static final boolean DEBUG_CAMERA_LIFT = false; - - public static final boolean DEBUG_WINDOW_STATE = false; - + String SYSTEM_DIALOG_REASON_KEY = "reason"; + String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; + String SYSTEM_DIALOG_REASON_DREAM = "dream"; + String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot"; + String TAG = "CentralSurfaces"; + boolean DEBUG = false; + boolean SPEW = false; + boolean DUMPTRUCK = true; // extra dumpsys info + boolean DEBUG_GESTURES = false; + boolean DEBUG_MEDIA_FAKE_ARTWORK = false; + boolean DEBUG_CAMERA_LIFT = false; + boolean DEBUG_WINDOW_STATE = false; // additional instrumentation for testing purposes; intended to be left on during development - public static final boolean CHATTY = DEBUG; - - public static final boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true; - - public static final String ACTION_FAKE_ARTWORK = "fake_artwork"; - - private static final int MSG_OPEN_SETTINGS_PANEL = 1002; - private static final int MSG_LAUNCH_TRANSITION_TIMEOUT = 1003; - // 1020-1040 reserved for BaseStatusBar - - // Time after we abort the launch transition. - static final long LAUNCH_TRANSITION_TIMEOUT_MS = 5000; - - protected static final boolean CLOSE_PANEL_WHEN_EMPTIED = true; - - /** - * The delay to reset the hint text when the hint animation is finished running. - */ - private static final int HINT_RESET_DELAY_MS = 1200; - - public static final int FADE_KEYGUARD_START_DELAY = 100; - public static final int FADE_KEYGUARD_DURATION = 300; - public static final int FADE_KEYGUARD_DURATION_PULSING = 96; - - public static final long[] CAMERA_LAUNCH_GESTURE_VIBRATION_TIMINGS = + boolean CHATTY = DEBUG; + boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true; + String ACTION_FAKE_ARTWORK = "fake_artwork"; + int FADE_KEYGUARD_START_DELAY = 100; + int FADE_KEYGUARD_DURATION = 300; + int FADE_KEYGUARD_DURATION_PULSING = 96; + long[] CAMERA_LAUNCH_GESTURE_VIBRATION_TIMINGS = new long[]{20, 20, 20, 20, 100, 20}; - public static final int[] CAMERA_LAUNCH_GESTURE_VIBRATION_AMPLITUDES = + int[] CAMERA_LAUNCH_GESTURE_VIBRATION_AMPLITUDES = new int[]{39, 82, 139, 213, 0, 127}; - /** - * If true, the system is in the half-boot-to-decryption-screen state. - * Prudently disable QS and notifications. - */ - public static final boolean ONLY_CORE_APPS; - /** If true, the lockscreen will show a distinct wallpaper */ - public static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true; - - private static final UiEventLogger sUiEventLogger = new UiEventLoggerImpl(); - - static { - boolean onlyCoreApps; - try { - IPackageManager packageManager = - IPackageManager.Stub.asInterface(ServiceManager.getService("package")); - onlyCoreApps = packageManager != null && packageManager.isOnlyCoreApps(); - } catch (RemoteException e) { - onlyCoreApps = false; - } - ONLY_CORE_APPS = onlyCoreApps; - } + boolean ENABLE_LOCKSCREEN_WALLPAPER = true; + // Time after we abort the launch transition. + long LAUNCH_TRANSITION_TIMEOUT_MS = 5000; + int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027; - private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; - private final DreamOverlayStateController mDreamOverlayStateController; - private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks; - private float mTransitionToFullShadeProgress = 0f; - private NotificationListContainer mNotifListContainer; - - private final KeyguardStateController.Callback mKeyguardStateControllerCallback = - new KeyguardStateController.Callback() { - @Override - public void onKeyguardShowingChanged() { - boolean occluded = mKeyguardStateController.isOccluded(); - mStatusBarHideIconsForBouncerManager.setIsOccludedAndTriggerUpdate(occluded); - mScrimController.setKeyguardOccluded(occluded); - } - }; - - void onStatusBarWindowStateChanged(@WindowVisibleState int state) { - updateBubblesVisibility(); - mStatusBarWindowState = state; - } + static final boolean CLOSE_PANEL_WHEN_EMPTIED = true; - void acquireGestureWakeLock(long time) { - mGestureWakeLock.acquire(time); + static String viewInfo(View v) { + return "[(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom() + + ") " + v.getWidth() + "x" + v.getHeight() + "]"; } - boolean setAppearance(int appearance) { - if (mAppearance != appearance) { - mAppearance = appearance; - return updateBarMode(barMode(isTransientShown(), appearance)); + static void dumpBarTransitions( + PrintWriter pw, String var, @Nullable BarTransitions transitions) { + pw.print(" "); + pw.print(var); + pw.print(".BarTransitions.mMode="); + if (transitions != null) { + pw.println(BarTransitions.modeToString(transitions.getMode())); + } else { + pw.println("Unknown"); } - - return false; - } - - int getBarMode() { - return mStatusBarMode; - } - - void resendMessage(int msg) { - mMessageRouter.cancelMessages(msg); - mMessageRouter.sendMessage(msg); - } - - void resendMessage(Object msg) { - mMessageRouter.cancelMessages(msg.getClass()); - mMessageRouter.sendMessage(msg); - } - - int getDisabled1() { - return mDisabled1; - } - - void setDisabled1(int disabled) { - mDisabled1 = disabled; - } - - int getDisabled2() { - return mDisabled2; - } - - void setDisabled2(int disabled) { - mDisabled2 = disabled; - } - - void setLastCameraLaunchSource(int source) { - mLastCameraLaunchSource = source; - } - - void setLaunchCameraOnFinishedGoingToSleep(boolean launch) { - mLaunchCameraOnFinishedGoingToSleep = launch; - } - - void setLaunchCameraOnFinishedWaking(boolean launch) { - mLaunchCameraWhenFinishedWaking = launch; - } - - void setLaunchEmergencyActionOnFinishedGoingToSleep(boolean launch) { - mLaunchEmergencyActionOnFinishedGoingToSleep = launch; - } - - void setLaunchEmergencyActionOnFinishedWaking(boolean launch) { - mLaunchEmergencyActionWhenFinishedWaking = launch; } - void setTopHidesStatusBar(boolean hides) { - mTopHidesStatusBar = hides; - } - - QSPanelController getQSPanelController() { - return mQSPanelController; - } - - /** */ - public void animateExpandNotificationsPanel() { - mCommandQueueCallbacks.animateExpandNotificationsPanel(); - } - - /** */ - public void animateExpandSettingsPanel(@Nullable String subpanel) { - mCommandQueueCallbacks.animateExpandSettingsPanel(subpanel); - } - - /** */ - public void animateCollapsePanels(int flags, boolean force) { - mCommandQueueCallbacks.animateCollapsePanels(flags, force); - } - - /** */ - public void togglePanel() { - mCommandQueueCallbacks.togglePanel(); - } /** - * The {@link StatusBarState} of the status bar. + * Returns an ActivityOptions bundle created using the given parameters. + * + * @param displayId The ID of the display to launch the activity in. Typically this would + * be the display the status bar is on. + * @param animationAdapter The animation adapter used to start this activity, or {@code null} + * for the default animation. */ - protected int mState; // TODO: remove this. Just use StatusBarStateController - protected boolean mBouncerShowing; - - private final PhoneStatusBarPolicy mIconPolicy; - - private final VolumeComponent mVolumeComponent; - private BrightnessMirrorController mBrightnessMirrorController; - private boolean mBrightnessMirrorVisible; - private BiometricUnlockController mBiometricUnlockController; - private final LightBarController mLightBarController; - private final Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy; - private final LockscreenGestureLogger mLockscreenGestureLogger; - @Nullable - protected LockscreenWallpaper mLockscreenWallpaper; - private final AutoHideController mAutoHideController; - - private final Point mCurrentDisplaySize = new Point(); - - protected NotificationShadeWindowView mNotificationShadeWindowView; - protected PhoneStatusBarView mStatusBarView; - private PhoneStatusBarViewController mPhoneStatusBarViewController; - private PhoneStatusBarTransitions mStatusBarTransitions; - private AuthRippleController mAuthRippleController; - @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING; - protected final NotificationShadeWindowController mNotificationShadeWindowController; - private final StatusBarWindowController mStatusBarWindowController; - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @VisibleForTesting - DozeServiceHost mDozeServiceHost; - private boolean mWakeUpComingFromTouch; - private PointF mWakeUpTouchLocation; - private LightRevealScrim mLightRevealScrim; - private PowerButtonReveal mPowerButtonReveal; - - private final Object mQueueLock = new Object(); - - private final PulseExpansionHandler mPulseExpansionHandler; - private final NotificationWakeUpCoordinator mWakeUpCoordinator; - private final KeyguardBypassController mKeyguardBypassController; - private final KeyguardStateController mKeyguardStateController; - private final HeadsUpManagerPhone mHeadsUpManager; - private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; - private final DynamicPrivacyController mDynamicPrivacyController; - private final FalsingCollector mFalsingCollector; - private final FalsingManager mFalsingManager; - private final BroadcastDispatcher mBroadcastDispatcher; - private final ConfigurationController mConfigurationController; - protected NotificationShadeWindowViewController mNotificationShadeWindowViewController; - private final DozeParameters mDozeParameters; - private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; - private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory; - private final PluginManager mPluginManager; - private final ShadeController mShadeController; - private final InitController mInitController; - - private final PluginDependencyProvider mPluginDependencyProvider; - private final KeyguardDismissUtil mKeyguardDismissUtil; - private final ExtensionController mExtensionController; - private final UserInfoControllerImpl mUserInfoControllerImpl; - private final DemoModeController mDemoModeController; - private final NotificationsController mNotificationsController; - private final OngoingCallController mOngoingCallController; - private final StatusBarSignalPolicy mStatusBarSignalPolicy; - private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; - - // expanded notifications - // the sliding/resizing panel within the notification window - protected NotificationPanelViewController mNotificationPanelViewController; - - // settings - private QSPanelController mQSPanelController; - - KeyguardIndicationController mKeyguardIndicationController; - - private View mReportRejectedTouch; - - private boolean mExpandedVisible; - - private final int[] mAbsPos = new int[2]; - - private final NotifShadeEventSource mNotifShadeEventSource; - protected final NotificationEntryManager mEntryManager; - private final NotificationGutsManager mGutsManager; - private final NotificationLogger mNotificationLogger; - private final NotificationViewHierarchyManager mViewHierarchyManager; - private final PanelExpansionStateManager mPanelExpansionStateManager; - private final KeyguardViewMediator mKeyguardViewMediator; - protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider; - private final BrightnessSliderController.Factory mBrightnessSliderFactory; - private final FeatureFlags mFeatureFlags; - private final FragmentService mFragmentService; - private final ScreenOffAnimationController mScreenOffAnimationController; - private final WallpaperController mWallpaperController; - private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; - private final MessageRouter mMessageRouter; - private final WallpaperManager mWallpaperManager; - - private CentralSurfacesComponent mCentralSurfacesComponent; - - // Flags for disabling the status bar - // Two variables becaseu the first one evidently ran out of room for new flags. - private int mDisabled1 = 0; - private int mDisabled2 = 0; - - /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */ - private @Appearance int mAppearance; - - private boolean mTransientShown; - - private final DisplayMetrics mDisplayMetrics; - - // XXX: gesture research - private final GestureRecorder mGestureRec = DEBUG_GESTURES - ? new GestureRecorder("/sdcard/statusbar_gestures.dat") - : null; - - private final ScreenPinningRequest mScreenPinningRequest; - - private final MetricsLogger mMetricsLogger; - - // ensure quick settings is disabled until the current user makes it through the setup wizard - @VisibleForTesting - protected boolean mUserSetup = false; - - @VisibleForTesting - public enum StatusBarUiEvent implements UiEventLogger.UiEventEnum { - @UiEvent(doc = "Secured lockscreen is opened.") - LOCKSCREEN_OPEN_SECURE(405), - - @UiEvent(doc = "Lockscreen without security is opened.") - LOCKSCREEN_OPEN_INSECURE(406), - - @UiEvent(doc = "Secured lockscreen is closed.") - LOCKSCREEN_CLOSE_SECURE(407), - - @UiEvent(doc = "Lockscreen without security is closed.") - LOCKSCREEN_CLOSE_INSECURE(408), - - @UiEvent(doc = "Secured bouncer is opened.") - BOUNCER_OPEN_SECURE(409), - - @UiEvent(doc = "Bouncer without security is opened.") - BOUNCER_OPEN_INSECURE(410), - - @UiEvent(doc = "Secured bouncer is closed.") - BOUNCER_CLOSE_SECURE(411), - - @UiEvent(doc = "Bouncer without security is closed.") - BOUNCER_CLOSE_INSECURE(412); - - private final int mId; - - StatusBarUiEvent(int id) { - mId = id; - } - - @Override - public int getId() { - return mId; - } + static Bundle getActivityOptions(int displayId, + @Nullable RemoteAnimationAdapter animationAdapter) { + ActivityOptions options = getDefaultActivityOptions(animationAdapter); + options.setLaunchDisplayId(displayId); + options.setCallerDisplayId(displayId); + return options.toBundle(); } - private Handler mMainHandler; - private final DelayableExecutor mMainExecutor; - - private int mInteractingWindows; - private @TransitionMode int mStatusBarMode; - - private final ViewMediatorCallback mKeyguardViewMediatorCallback; - private final ScrimController mScrimController; - protected DozeScrimController mDozeScrimController; - private final Executor mUiBgExecutor; - - protected boolean mDozing; - private boolean mIsFullscreen; - - boolean mCloseQsBeforeScreenOff; - - private final NotificationMediaManager mMediaManager; - private final NotificationLockscreenUserManager mLockscreenUserManager; - private final NotificationRemoteInputManager mRemoteInputManager; - private boolean mWallpaperSupported; - - private Runnable mLaunchTransitionEndRunnable; - private Runnable mLaunchTransitionCancelRunnable; - private boolean mLaunchCameraWhenFinishedWaking; - private boolean mLaunchCameraOnFinishedGoingToSleep; - private boolean mLaunchEmergencyActionWhenFinishedWaking; - private boolean mLaunchEmergencyActionOnFinishedGoingToSleep; - private int mLastCameraLaunchSource; - protected PowerManager.WakeLock mGestureWakeLock; - - private final int[] mTmpInt2 = new int[2]; - - // Fingerprint (as computed by getLoggingFingerprint() of the last logged state. - private int mLastLoggedStateFingerprint; - private boolean mTopHidesStatusBar; - private boolean mStatusBarWindowHidden; - private boolean mIsLaunchingActivityOverLockscreen; - - private final UserSwitcherController mUserSwitcherController; - private final NetworkController mNetworkController; - private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); - protected final BatteryController mBatteryController; - protected boolean mPanelExpanded; - private UiModeManager mUiModeManager; - private LogMaker mStatusBarStateLog; - protected final NotificationIconAreaController mNotificationIconAreaController; - @Nullable private View mAmbientIndicationContainer; - private final SysuiColorExtractor mColorExtractor; - private final ScreenLifecycle mScreenLifecycle; - private final WakefulnessLifecycle mWakefulnessLifecycle; - - private boolean mNoAnimationOnNextBarModeChange; - private final SysuiStatusBarStateController mStatusBarStateController; - - private final ActivityLaunchAnimator mActivityLaunchAnimator; - private NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider; - protected NotificationPresenter mPresenter; - private NotificationActivityStarter mNotificationActivityStarter; - private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy; - private final Optional<BubblesManager> mBubblesManagerOptional; - private final Optional<Bubbles> mBubblesOptional; - private final Bubbles.BubbleExpandListener mBubbleExpandListener; - private final Optional<StartingSurface> mStartingSurfaceOptional; - private final NotifPipelineFlags mNotifPipelineFlags; - - private final ActivityIntentHelper mActivityIntentHelper; - private NotificationStackScrollLayoutController mStackScrollerController; - - private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener = - (extractor, which) -> updateTheme(); - - private final InteractionJankMonitor mJankMonitor; - - /** - * Public constructor for CentralSurfaces. + * Returns an ActivityOptions bundle created using the given parameters. * - * CentralSurfaces is considered optional, and therefore can not be marked as @Inject directly. - * Instead, an @Provide method is included. See {@link StatusBarPhoneModule}. + * @param displayId The ID of the display to launch the activity in. Typically this + * would be the + * display the status bar is on. + * @param animationAdapter The animation adapter used to start this activity, or {@code null} + * for the default animation. + * @param isKeyguardShowing Whether keyguard is currently showing. + * @param eventTime The event time in milliseconds since boot, not including sleep. See + * {@link ActivityOptions#setSourceInfo}. */ - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - public CentralSurfaces( - Context context, - NotificationsController notificationsController, - FragmentService fragmentService, - LightBarController lightBarController, - AutoHideController autoHideController, - StatusBarWindowController statusBarWindowController, - StatusBarWindowStateController statusBarWindowStateController, - KeyguardUpdateMonitor keyguardUpdateMonitor, - StatusBarSignalPolicy statusBarSignalPolicy, - PulseExpansionHandler pulseExpansionHandler, - NotificationWakeUpCoordinator notificationWakeUpCoordinator, - KeyguardBypassController keyguardBypassController, - KeyguardStateController keyguardStateController, - HeadsUpManagerPhone headsUpManagerPhone, - DynamicPrivacyController dynamicPrivacyController, - FalsingManager falsingManager, - FalsingCollector falsingCollector, - BroadcastDispatcher broadcastDispatcher, - NotifShadeEventSource notifShadeEventSource, - NotificationEntryManager notificationEntryManager, - NotificationGutsManager notificationGutsManager, - NotificationLogger notificationLogger, - NotificationInterruptStateProvider notificationInterruptStateProvider, - NotificationViewHierarchyManager notificationViewHierarchyManager, - PanelExpansionStateManager panelExpansionStateManager, - KeyguardViewMediator keyguardViewMediator, - DisplayMetrics displayMetrics, - MetricsLogger metricsLogger, - @UiBackground Executor uiBgExecutor, - NotificationMediaManager notificationMediaManager, - NotificationLockscreenUserManager lockScreenUserManager, - NotificationRemoteInputManager remoteInputManager, - UserSwitcherController userSwitcherController, - NetworkController networkController, - BatteryController batteryController, - SysuiColorExtractor colorExtractor, - ScreenLifecycle screenLifecycle, - WakefulnessLifecycle wakefulnessLifecycle, - SysuiStatusBarStateController statusBarStateController, - Optional<BubblesManager> bubblesManagerOptional, - Optional<Bubbles> bubblesOptional, - VisualStabilityManager visualStabilityManager, - DeviceProvisionedController deviceProvisionedController, - NavigationBarController navigationBarController, - AccessibilityFloatingMenuController accessibilityFloatingMenuController, - Lazy<AssistManager> assistManagerLazy, - ConfigurationController configurationController, - NotificationShadeWindowController notificationShadeWindowController, - DozeParameters dozeParameters, - ScrimController scrimController, - Lazy<LockscreenWallpaper> lockscreenWallpaperLazy, - LockscreenGestureLogger lockscreenGestureLogger, - Lazy<BiometricUnlockController> biometricUnlockControllerLazy, - DozeServiceHost dozeServiceHost, - PowerManager powerManager, - ScreenPinningRequest screenPinningRequest, - DozeScrimController dozeScrimController, - VolumeComponent volumeComponent, - CommandQueue commandQueue, - CentralSurfacesComponent.Factory centralSurfacesComponentFactory, - PluginManager pluginManager, - ShadeController shadeController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager, - ViewMediatorCallback viewMediatorCallback, - InitController initController, - @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler, - PluginDependencyProvider pluginDependencyProvider, - KeyguardDismissUtil keyguardDismissUtil, - ExtensionController extensionController, - UserInfoControllerImpl userInfoControllerImpl, - PhoneStatusBarPolicy phoneStatusBarPolicy, - KeyguardIndicationController keyguardIndicationController, - DemoModeController demoModeController, - Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy, - StatusBarTouchableRegionManager statusBarTouchableRegionManager, - NotificationIconAreaController notificationIconAreaController, - BrightnessSliderController.Factory brightnessSliderFactory, - ScreenOffAnimationController screenOffAnimationController, - WallpaperController wallpaperController, - OngoingCallController ongoingCallController, - StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, - LockscreenShadeTransitionController lockscreenShadeTransitionController, - FeatureFlags featureFlags, - KeyguardUnlockAnimationController keyguardUnlockAnimationController, - @Main Handler mainHandler, - @Main DelayableExecutor delayableExecutor, - @Main MessageRouter messageRouter, - WallpaperManager wallpaperManager, - Optional<StartingSurface> startingSurfaceOptional, - ActivityLaunchAnimator activityLaunchAnimator, - NotifPipelineFlags notifPipelineFlags, - InteractionJankMonitor jankMonitor, - DeviceStateManager deviceStateManager, - DreamOverlayStateController dreamOverlayStateController, - WiredChargingRippleController wiredChargingRippleController) { - super(context); - mNotificationsController = notificationsController; - mFragmentService = fragmentService; - mLightBarController = lightBarController; - mAutoHideController = autoHideController; - mStatusBarWindowController = statusBarWindowController; - mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mPulseExpansionHandler = pulseExpansionHandler; - mWakeUpCoordinator = notificationWakeUpCoordinator; - mKeyguardBypassController = keyguardBypassController; - mKeyguardStateController = keyguardStateController; - mHeadsUpManager = headsUpManagerPhone; - mKeyguardIndicationController = keyguardIndicationController; - mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; - mDynamicPrivacyController = dynamicPrivacyController; - mFalsingCollector = falsingCollector; - mFalsingManager = falsingManager; - mBroadcastDispatcher = broadcastDispatcher; - mNotifShadeEventSource = notifShadeEventSource; - mEntryManager = notificationEntryManager; - mGutsManager = notificationGutsManager; - mNotificationLogger = notificationLogger; - mNotificationInterruptStateProvider = notificationInterruptStateProvider; - mViewHierarchyManager = notificationViewHierarchyManager; - mPanelExpansionStateManager = panelExpansionStateManager; - mKeyguardViewMediator = keyguardViewMediator; - mDisplayMetrics = displayMetrics; - mMetricsLogger = metricsLogger; - mUiBgExecutor = uiBgExecutor; - mMediaManager = notificationMediaManager; - mLockscreenUserManager = lockScreenUserManager; - mRemoteInputManager = remoteInputManager; - mUserSwitcherController = userSwitcherController; - mNetworkController = networkController; - mBatteryController = batteryController; - mColorExtractor = colorExtractor; - mScreenLifecycle = screenLifecycle; - mWakefulnessLifecycle = wakefulnessLifecycle; - mStatusBarStateController = statusBarStateController; - mBubblesManagerOptional = bubblesManagerOptional; - mBubblesOptional = bubblesOptional; - mVisualStabilityManager = visualStabilityManager; - mDeviceProvisionedController = deviceProvisionedController; - mNavigationBarController = navigationBarController; - mAccessibilityFloatingMenuController = accessibilityFloatingMenuController; - mAssistManagerLazy = assistManagerLazy; - mConfigurationController = configurationController; - mNotificationShadeWindowController = notificationShadeWindowController; - mDozeServiceHost = dozeServiceHost; - mPowerManager = powerManager; - mDozeParameters = dozeParameters; - mScrimController = scrimController; - mLockscreenWallpaperLazy = lockscreenWallpaperLazy; - mLockscreenGestureLogger = lockscreenGestureLogger; - mScreenPinningRequest = screenPinningRequest; - mDozeScrimController = dozeScrimController; - mBiometricUnlockControllerLazy = biometricUnlockControllerLazy; - mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy; - mVolumeComponent = volumeComponent; - mCommandQueue = commandQueue; - mCentralSurfacesComponentFactory = centralSurfacesComponentFactory; - mPluginManager = pluginManager; - mShadeController = shadeController; - mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; - mKeyguardViewMediatorCallback = viewMediatorCallback; - mInitController = initController; - mPluginDependencyProvider = pluginDependencyProvider; - mKeyguardDismissUtil = keyguardDismissUtil; - mExtensionController = extensionController; - mUserInfoControllerImpl = userInfoControllerImpl; - mIconPolicy = phoneStatusBarPolicy; - mDemoModeController = demoModeController; - mNotificationIconAreaController = notificationIconAreaController; - mBrightnessSliderFactory = brightnessSliderFactory; - mWallpaperController = wallpaperController; - mOngoingCallController = ongoingCallController; - mStatusBarSignalPolicy = statusBarSignalPolicy; - mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; - mFeatureFlags = featureFlags; - mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; - mMainHandler = mainHandler; - mMainExecutor = delayableExecutor; - mMessageRouter = messageRouter; - mWallpaperManager = wallpaperManager; - mJankMonitor = jankMonitor; - mDreamOverlayStateController = dreamOverlayStateController; - - mLockscreenShadeTransitionController = lockscreenShadeTransitionController; - mStartingSurfaceOptional = startingSurfaceOptional; - mNotifPipelineFlags = notifPipelineFlags; - lockscreenShadeTransitionController.setCentralSurfaces(this); - statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged); - - mScreenOffAnimationController = screenOffAnimationController; - - mPanelExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged); - - mBubbleExpandListener = - (isExpanding, key) -> mContext.getMainExecutor().execute(() -> { - mNotificationsController.requestNotificationUpdate("onBubbleExpandChanged"); - updateScrimController(); - }); - - mActivityIntentHelper = new ActivityIntentHelper(mContext); - mActivityLaunchAnimator = activityLaunchAnimator; - - // The status bar background may need updating when the ongoing call status changes. - mOngoingCallController.addCallback((animate) -> maybeUpdateBarMode()); - - // TODO(b/190746471): Find a better home for this. - DateTimeView.setReceiverHandler(timeTickHandler); - - mMessageRouter.subscribeTo(KeyboardShortcutsMessage.class, - data -> toggleKeyboardShortcuts(data.mDeviceId)); - mMessageRouter.subscribeTo(MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU, - id -> dismissKeyboardShortcuts()); - mMessageRouter.subscribeTo(AnimateExpandSettingsPanelMessage.class, - data -> mCommandQueueCallbacks.animateExpandSettingsPanel(data.mSubpanel)); - mMessageRouter.subscribeTo(MSG_LAUNCH_TRANSITION_TIMEOUT, - id -> onLaunchTransitionTimeout()); - - deviceStateManager.registerCallback(mMainExecutor, - new FoldStateListener(mContext, this::onFoldedStateChanged)); - wiredChargingRippleController.registerCallbacks(); - } - - @Override - public void start() { - mScreenLifecycle.addObserver(mScreenObserver); - mWakefulnessLifecycle.addObserver(mWakefulnessObserver); - mUiModeManager = mContext.getSystemService(UiModeManager.class); - if (mBubblesOptional.isPresent()) { - mBubblesOptional.get().setExpandListener(mBubbleExpandListener); - } - - mStatusBarSignalPolicy.init(); - mKeyguardIndicationController.init(); - - mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener); - mStatusBarStateController.addCallback(mStateListener, - SysuiStatusBarStateController.RANK_STATUS_BAR); - - mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - mDreamManager = IDreamManager.Stub.asInterface( - ServiceManager.checkService(DreamService.DREAM_SERVICE)); - - mDisplay = mContext.getDisplay(); - mDisplayId = mDisplay.getDisplayId(); - updateDisplaySize(); - mStatusBarHideIconsForBouncerManager.setDisplayId(mDisplayId); - - // start old BaseStatusBar.start(). - mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); - mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService( - Context.DEVICE_POLICY_SERVICE); - - mAccessibilityManager = (AccessibilityManager) - mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); - - mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController); - mBarService = IStatusBarService.Stub.asInterface( - ServiceManager.getService(Context.STATUS_BAR_SERVICE)); - - mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - mWallpaperSupported = mWallpaperManager.isWallpaperSupported(); - - RegisterStatusBarResult result = null; - try { - result = mBarService.registerStatusBar(mCommandQueue); - } catch (RemoteException ex) { - ex.rethrowFromSystemServer(); - } - - createAndAddWindows(result); - - if (mWallpaperSupported) { - // Make sure we always have the most current wallpaper info. - IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); - mBroadcastDispatcher.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter, - null /* handler */, UserHandle.ALL); - mWallpaperChangedReceiver.onReceive(mContext, null); - } else if (DEBUG) { - Log.v(TAG, "start(): no wallpaper service "); - } - - // Set up the initial notification state. This needs to happen before CommandQueue.disable() - setUpPresenter(); - - if (containsType(result.mTransientBarTypes, ITYPE_STATUS_BAR)) { - showTransientUnchecked(); - } - mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance, - result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior, - result.mRequestedVisibilities, result.mPackageName); - - // StatusBarManagerService has a back up of IME token and it's restored here. - mCommandQueueCallbacks.setImeWindowStatus(mDisplayId, result.mImeToken, - result.mImeWindowVis, result.mImeBackDisposition, result.mShowImeSwitcher); - - // Set up the initial icon state - int numIcons = result.mIcons.size(); - for (int i = 0; i < numIcons; i++) { - mCommandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i)); - } - - if (DEBUG) { - Log.d(TAG, String.format( - "init: icons=%d disabled=0x%08x lights=0x%08x imeButton=0x%08x", - numIcons, - result.mDisabledFlags1, - result.mAppearance, - result.mImeWindowVis)); - } - - IntentFilter internalFilter = new IntentFilter(); - internalFilter.addAction(BANNER_ACTION_CANCEL); - internalFilter.addAction(BANNER_ACTION_SETUP); - mContext.registerReceiver(mBannerActionBroadcastReceiver, internalFilter, PERMISSION_SELF, - null, Context.RECEIVER_EXPORTED_UNAUDITED); - - if (mWallpaperSupported) { - IWallpaperManager wallpaperManager = IWallpaperManager.Stub.asInterface( - ServiceManager.getService(Context.WALLPAPER_SERVICE)); - try { - wallpaperManager.setInAmbientMode(false /* ambientMode */, 0L /* duration */); - } catch (RemoteException e) { - // Just pass, nothing critical. - } - } - - // end old BaseStatusBar.start(). - - // Lastly, call to the icon policy to install/update all the icons. - mIconPolicy.init(); - - mKeyguardStateController.addCallback(new KeyguardStateController.Callback() { - @Override - public void onUnlockedChanged() { - logStateToEventlog(); - } - }); - startKeyguard(); - - mKeyguardUpdateMonitor.registerCallback(mUpdateCallback); - mDozeServiceHost.initialize( - this, - mStatusBarKeyguardViewManager, - mNotificationShadeWindowViewController, - mNotificationPanelViewController, - mAmbientIndicationContainer); - updateLightRevealScrimVisibility(); - - mConfigurationController.addCallback(mConfigurationListener); - - mBatteryController.observe(mLifecycle, mBatteryStateChangeCallback); - mLifecycle.setCurrentState(RESUMED); - - mAccessibilityFloatingMenuController.init(); - - // set the initial view visibility - int disabledFlags1 = result.mDisabledFlags1; - int disabledFlags2 = result.mDisabledFlags2; - mInitController.addPostInitTask( - () -> setUpDisableFlags(disabledFlags1, disabledFlags2)); - - mFalsingManager.addFalsingBeliefListener(mFalsingBeliefListener); - - mPluginManager.addPluginListener( - new PluginListener<OverlayPlugin>() { - private final ArraySet<OverlayPlugin> mOverlays = new ArraySet<>(); - - @Override - public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) { - mMainExecutor.execute( - () -> plugin.setup(getNotificationShadeWindowView(), - getNavigationBarView(), - new Callback(plugin), mDozeParameters)); - } - - @Override - public void onPluginDisconnected(OverlayPlugin plugin) { - mMainExecutor.execute(() -> { - mOverlays.remove(plugin); - mNotificationShadeWindowController - .setForcePluginOpen(mOverlays.size() != 0, this); - }); - } - - class Callback implements OverlayPlugin.Callback { - private final OverlayPlugin mPlugin; - - Callback(OverlayPlugin plugin) { - mPlugin = plugin; - } - - @Override - public void onHoldStatusBarOpenChange() { - if (mPlugin.holdStatusBarOpen()) { - mOverlays.add(mPlugin); - } else { - mOverlays.remove(mPlugin); - } - mMainExecutor.execute(() -> { - mNotificationShadeWindowController - .setStateListener(b -> mOverlays.forEach( - o -> o.setCollapseDesired(b))); - mNotificationShadeWindowController - .setForcePluginOpen(mOverlays.size() != 0, this); - }); - } - } - }, OverlayPlugin.class, true /* Allow multiple plugins */); - - mStartingSurfaceOptional.ifPresent(startingSurface -> startingSurface.setSysuiProxy( - (requestTopUi, componentTag) -> mMainExecutor.execute(() -> - mNotificationShadeWindowController.setRequestTopUi( - requestTopUi, componentTag)))); - } - - private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) { - Trace.beginSection("CentralSurfaces#onFoldedStateChanged"); - onFoldedStateChangedInternal(isFolded, willGoToSleep); - Trace.endSection(); - } - - private void onFoldedStateChangedInternal(boolean isFolded, boolean willGoToSleep) { - // Folded state changes are followed by a screen off event. - // By default turning off the screen also closes the shade. - // We want to make sure that the shade status is kept after - // folding/unfolding. - boolean isShadeOpen = mShadeController.isShadeOpen(); - boolean leaveOpen = isShadeOpen && !willGoToSleep; - if (DEBUG) { - Log.d(TAG, String.format( - "#onFoldedStateChanged(): " - + "isFolded=%s, " - + "willGoToSleep=%s, " - + "isShadeOpen=%s, " - + "leaveOpen=%s", - isFolded, willGoToSleep, isShadeOpen, leaveOpen)); - } - if (leaveOpen) { - mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); - if (mKeyguardStateController.isShowing()) { - // When device state changes on keyguard we don't want to keep the state of - // the shade and instead we open clean state of keyguard with shade closed. - // Normally some parts of QS state (like expanded/collapsed) are persisted and - // that causes incorrect UI rendering, especially when changing state with QS - // expanded. To prevent that we can close QS which resets QS and some parts of - // the shade to its default state. Read more in b/201537421 - mCloseQsBeforeScreenOff = true; - } - } + static Bundle getActivityOptions(int displayId, + @Nullable RemoteAnimationAdapter animationAdapter, boolean isKeyguardShowing, + long eventTime) { + ActivityOptions options = getDefaultActivityOptions(animationAdapter); + options.setSourceInfo(isKeyguardShowing ? ActivityOptions.SourceInfo.TYPE_LOCKSCREEN + : ActivityOptions.SourceInfo.TYPE_NOTIFICATION, eventTime); + options.setLaunchDisplayId(displayId); + options.setCallerDisplayId(displayId); + return options.toBundle(); } - // ================================================================================ - // Constructing the view - // ================================================================================ - protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) { - updateDisplaySize(); // populates mDisplayMetrics - updateResources(); - updateTheme(); - - inflateStatusBarWindow(); - mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener()); - mWallpaperController.setRootView(mNotificationShadeWindowView); - - // TODO: Deal with the ugliness that comes from having some of the status bar broken out - // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot. - mNotificationLogger.setUpWithContainer(mNotifListContainer); - mNotificationIconAreaController.setupShelf(mNotificationShelfController); - mPanelExpansionStateManager.addExpansionListener(mWakeUpCoordinator); - mUserSwitcherController.init(mNotificationShadeWindowView); - - // Allow plugins to reference DarkIconDispatcher and StatusBarStateController - mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class); - mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class); - - // Set up CollapsedStatusBarFragment and PhoneStatusBarView - StatusBarInitializer initializer = mCentralSurfacesComponent.getStatusBarInitializer(); - initializer.setStatusBarViewUpdatedListener( - (statusBarView, statusBarViewController, statusBarTransitions) -> { - mStatusBarView = statusBarView; - mPhoneStatusBarViewController = statusBarViewController; - mStatusBarTransitions = statusBarTransitions; - mNotificationShadeWindowViewController - .setStatusBarViewController(mPhoneStatusBarViewController); - // Ensure we re-propagate panel expansion values to the panel controller and - // any listeners it may have, such as PanelBar. This will also ensure we - // re-display the notification panel if necessary (for example, if - // a heads-up notification was being displayed and should continue being - // displayed). - mNotificationPanelViewController.updatePanelExpansionAndVisibility(); - setBouncerShowingForStatusBarComponents(mBouncerShowing); - checkBarModes(); - }); - initializer.initializeStatusBar(mCentralSurfacesComponent); - - mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView); - mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener()); - if (!mNotifPipelineFlags.isNewPipelineEnabled()) { - mHeadsUpManager.addListener(mVisualStabilityManager); - } - mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); - - createNavigationBar(result); - - if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) { - mLockscreenWallpaper = mLockscreenWallpaperLazy.get(); - } - - mNotificationPanelViewController.setKeyguardIndicationController( - mKeyguardIndicationController); - - mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById( - R.id.ambient_indication_container); - - mAutoHideController.setStatusBar(new AutoHideUiElement() { - @Override - public void synchronizeState() { - checkBarModes(); - } - - @Override - public boolean shouldHideOnTouch() { - return !mRemoteInputManager.isRemoteInputActive(); - } - - @Override - public boolean isVisible() { - return isTransientShown(); - } - - @Override - public void hide() { - clearTransient(); - } - }); - - ScrimView scrimBehind = mNotificationShadeWindowView.findViewById(R.id.scrim_behind); - ScrimView notificationsScrim = mNotificationShadeWindowView - .findViewById(R.id.scrim_notifications); - ScrimView scrimInFront = mNotificationShadeWindowView.findViewById(R.id.scrim_in_front); - - mScrimController.setScrimVisibleListener(scrimsVisible -> { - mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible); - }); - mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront); - - mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim); - mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> { - Runnable updateOpaqueness = () -> { - mNotificationShadeWindowController.setLightRevealScrimOpaque( - mLightRevealScrim.isScrimOpaque()); - mScreenOffAnimationController - .onScrimOpaqueChanged(mLightRevealScrim.isScrimOpaque()); - }; - if (opaque) { - // Delay making the view opaque for a frame, because it needs some time to render - // otherwise this can lead to a flicker where the scrim doesn't cover the screen - mLightRevealScrim.post(updateOpaqueness); + static ActivityOptions getDefaultActivityOptions( + @Nullable RemoteAnimationAdapter animationAdapter) { + ActivityOptions options; + if (animationAdapter != null) { + if (ENABLE_SHELL_TRANSITIONS) { + options = ActivityOptions.makeRemoteTransition( + RemoteTransitionAdapter.adaptRemoteAnimation(animationAdapter)); } else { - updateOpaqueness.run(); + options = ActivityOptions.makeRemoteAnimation(animationAdapter); } - }); - - mScreenOffAnimationController.initialize(this, mLightRevealScrim); - updateLightRevealScrimVisibility(); - - mNotificationPanelViewController.initDependencies( - this, - this::makeExpandedInvisible, - mNotificationShelfController); - - BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop); - mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front), - backdrop.findViewById(R.id.backdrop_back), mScrimController, mLockscreenWallpaper); - float maxWallpaperZoom = mContext.getResources().getFloat( - com.android.internal.R.dimen.config_wallpaperMaxScale); - mNotificationShadeDepthControllerLazy.get().addListener(depth -> { - float scale = MathUtils.lerp(maxWallpaperZoom, 1f, depth); - backdrop.setPivotX(backdrop.getWidth() / 2f); - backdrop.setPivotY(backdrop.getHeight() / 2f); - backdrop.setScaleX(scale); - backdrop.setScaleY(scale); - }); - - mNotificationPanelViewController.setUserSetupComplete(mUserSetup); - - // Set up the quick settings tile panel - final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame); - if (container != null) { - FragmentHostManager fragmentHostManager = FragmentHostManager.get(container); - ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame, - mExtensionController - .newExtension(QS.class) - .withPlugin(QS.class) - .withDefault(this::createDefaultQSFragment) - .build()); - mBrightnessMirrorController = new BrightnessMirrorController( - mNotificationShadeWindowView, - mNotificationPanelViewController, - mNotificationShadeDepthControllerLazy.get(), - mBrightnessSliderFactory, - (visible) -> { - mBrightnessMirrorVisible = visible; - updateScrimController(); - }); - fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> { - QS qs = (QS) f; - if (qs instanceof QSFragment) { - mQSPanelController = ((QSFragment) qs).getQSPanelController(); - ((QSFragment) qs).setBrightnessMirrorController(mBrightnessMirrorController); - } - }); - } - - mReportRejectedTouch = mNotificationShadeWindowView - .findViewById(R.id.report_rejected_touch); - if (mReportRejectedTouch != null) { - updateReportRejectedTouchVisibility(); - mReportRejectedTouch.setOnClickListener(v -> { - Uri session = mFalsingManager.reportRejectedTouch(); - if (session == null) { return; } - - StringWriter message = new StringWriter(); - message.write("Build info: "); - message.write(SystemProperties.get("ro.build.description")); - message.write("\nSerial number: "); - message.write(SystemProperties.get("ro.serialno")); - message.write("\n"); - - startActivityDismissingKeyguard(Intent.createChooser(new Intent(Intent.ACTION_SEND) - .setType("*/*") - .putExtra(Intent.EXTRA_SUBJECT, "Rejected touch report") - .putExtra(Intent.EXTRA_STREAM, session) - .putExtra(Intent.EXTRA_TEXT, message.toString()), - "Share rejected touch report") - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), - true /* onlyProvisioned */, true /* dismissShade */); - }); - } - - if (!mPowerManager.isInteractive()) { - mBroadcastReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); - } - mGestureWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, - "sysui:GestureWakeLock"); - - // receive broadcasts - registerBroadcastReceiver(); - - IntentFilter demoFilter = new IntentFilter(); - if (DEBUG_MEDIA_FAKE_ARTWORK) { - demoFilter.addAction(ACTION_FAKE_ARTWORK); + } else { + options = ActivityOptions.makeBasic(); } - mContext.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter, - android.Manifest.permission.DUMP, null, - Context.RECEIVER_EXPORTED_UNAUDITED); - - // listen for USER_SETUP_COMPLETE setting (per-user) - mDeviceProvisionedController.addCallback(mUserSetupObserver); - mUserSetupObserver.onUserSetupChanged(); - - // disable profiling bars, since they overlap and clutter the output on app windows - ThreadedRenderer.overrideProperty("disableProfileBars", "true"); - - // Private API call to make the shadows look better for Recents - ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f)); + options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR); + return options; } - /** - * When swiping up to dismiss the lock screen, the panel expansion fraction goes from 1f to 0f. - * This results in the clock/notifications/other content disappearing off the top of the screen. - * - * We also use the expansion fraction to animate in the app/launcher surface from the bottom of - * the screen, 'pushing' off the notifications and other content. To do this, we dispatch the - * expansion fraction to the KeyguardViewMediator if we're in the process of dismissing the - * keyguard. + * @return a PackageManager for userId or if userId is < 0 (USER_ALL etc) then + * return PackageManager for mContext */ - private void dispatchPanelExpansionForKeyguardDismiss(float fraction, boolean trackingTouch) { - // Things that mean we're not swiping to dismiss the keyguard, and should ignore this - // expansion: - // - Keyguard isn't even visible. - // - Keyguard is occluded. Expansion changes here are the shade being expanded over the - // occluding activity. - // - Keyguard is visible, but can't be dismissed (swiping up will show PIN/password prompt). - // - The SIM is locked, you can't swipe to unlock. If the SIM is locked but there is no - // device lock set, canDismissLockScreen returns true even though you should not be able - // to dismiss the lock screen until entering the SIM PIN. - // - QS is expanded and we're swiping - swiping up now will hide QS, not dismiss the - // keyguard. - if (!isKeyguardShowing() - || isOccluded() - || !mKeyguardStateController.canDismissLockScreen() - || mKeyguardViewMediator.isAnySimPinSecure() - || (mNotificationPanelViewController.isQsExpanded() && trackingTouch)) { - return; - } - - // Otherwise, we should let the keyguard know about this if we're tracking touch, or if we - // are already animating the keyguard dismiss (since we will need to either finish or cancel - // the animation). - if (trackingTouch - || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe() - || mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) { - mKeyguardStateController.notifyKeyguardDismissAmountChanged( - 1f - fraction, trackingTouch); - } - } - - private void onPanelExpansionChanged(PanelExpansionChangeEvent event) { - float fraction = event.getFraction(); - boolean tracking = event.getTracking(); - dispatchPanelExpansionForKeyguardDismiss(fraction, tracking); - - if (fraction == 0 || fraction == 1) { - if (getNavigationBarView() != null) { - getNavigationBarView().onStatusBarPanelStateChanged(); - } - if (getNotificationPanelViewController() != null) { - getNotificationPanelViewController().updateSystemUiStateFlags(); + static PackageManager getPackageManagerForUser(Context context, int userId) { + Context contextForUser = context; + // UserHandle defines special userId as negative values, e.g. USER_ALL + if (userId >= 0) { + try { + // Create a context for the correct user so if a package isn't installed + // for user 0 we can still load information about the package. + contextForUser = + context.createPackageContextAsUser(context.getPackageName(), + Context.CONTEXT_RESTRICTED, + new UserHandle(userId)); + } catch (PackageManager.NameNotFoundException e) { + // Shouldn't fail to find the package name for system ui. } } + return contextForUser.getPackageManager(); } - @NonNull - @Override - public Lifecycle getLifecycle() { - return mLifecycle; - } - - @VisibleForTesting - protected void registerBroadcastReceiver() { - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - filter.addAction(Intent.ACTION_SCREEN_OFF); - mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, null, UserHandle.ALL); - } + void animateExpandNotificationsPanel(); - protected QS createDefaultQSFragment() { - return FragmentHostManager.get(mNotificationShadeWindowView).create(QSFragment.class); - } + void animateExpandSettingsPanel(@Nullable String subpanel); - private void setUpPresenter() { - // Set up the initial notification state. - mActivityLaunchAnimator.setCallback(mActivityLaunchAnimatorCallback); - mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener); - mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider( - mNotificationShadeWindowViewController, - mNotifListContainer, - mHeadsUpManager, - mJankMonitor); - mNotificationShelfController.setOnActivatedListener(mPresenter); - mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController); - mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter); - mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); - mNotificationsController.initialize( - mPresenter, - mNotifListContainer, - mStackScrollerController.getNotifStackController(), - mNotificationActivityStarter, - mCentralSurfacesComponent.getBindRowCallback()); - } + void animateCollapsePanels(int flags, boolean force); - /** - * Post-init task of {@link #start()} - * @param state1 disable1 flags - * @param state2 disable2 flags - */ - protected void setUpDisableFlags(int state1, int state2) { - mCommandQueue.disable(mDisplayId, state1, state2, false /* animate */); - } + void collapsePanelOnMainThread(); - /** - * Ask the display to wake up if currently dozing, else do nothing - * - * @param time when to wake up - * @param where the view requesting the wakeup - * @param why the reason for the wake up - */ - public void wakeUpIfDozing(long time, View where, String why) { - if (mDozing && mScreenOffAnimationController.allowWakeUpIfDozing()) { - mPowerManager.wakeUp( - time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why); - mWakeUpComingFromTouch = true; - where.getLocationInWindow(mTmpInt2); - - // NOTE, the incoming view can sometimes be the entire container... unsure if - // this location is valuable enough - mWakeUpTouchLocation = new PointF(mTmpInt2[0] + where.getWidth() / 2, - mTmpInt2[1] + where.getHeight() / 2); - mFalsingCollector.onScreenOnFromTouch(); - } - } + void collapsePanelWithDuration(int duration); - // TODO(b/117478341): This was left such that CarStatusBar can override this method. - // Try to remove this. - protected void createNavigationBar(@Nullable RegisterStatusBarResult result) { - mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result); - } + void togglePanel(); - /** - * Returns the {@link android.view.View.OnTouchListener} that will be invoked when the - * background window of the status bar is clicked. - */ - protected View.OnTouchListener getStatusBarWindowTouchListener() { - return (v, event) -> { - mAutoHideController.checkUserAutoHide(event); - mRemoteInputManager.checkRemoteInputOutside(event); - if (event.getAction() == MotionEvent.ACTION_UP) { - if (mExpandedVisible) { - mShadeController.animateCollapsePanels(); - } - } - return mNotificationShadeWindowView.onTouchEvent(event); - }; - } + void start(); - private void inflateStatusBarWindow() { - if (mCentralSurfacesComponent != null) { - // Tear down - for (CentralSurfacesComponent.Startable s : mCentralSurfacesComponent.getStartables()) { - s.stop(); - } - } - mCentralSurfacesComponent = mCentralSurfacesComponentFactory.create(); - mFragmentService.addFragmentInstantiationProvider(mCentralSurfacesComponent); - - mNotificationShadeWindowView = mCentralSurfacesComponent.getNotificationShadeWindowView(); - mNotificationShadeWindowViewController = mCentralSurfacesComponent - .getNotificationShadeWindowViewController(); - mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); - mNotificationShadeWindowViewController.setupExpandedStatusBar(); - mNotificationPanelViewController = - mCentralSurfacesComponent.getNotificationPanelViewController(); - mCentralSurfacesComponent.getLockIconViewController().init(); - mStackScrollerController = - mCentralSurfacesComponent.getNotificationStackScrollLayoutController(); - mStackScroller = mStackScrollerController.getView(); - mNotifListContainer = mCentralSurfacesComponent.getNotificationListContainer(); - mPresenter = mCentralSurfacesComponent.getNotificationPresenter(); - mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter(); - mNotificationShelfController = mCentralSurfacesComponent.getNotificationShelfController(); - mAuthRippleController = mCentralSurfacesComponent.getAuthRippleController(); - mAuthRippleController.init(); - - mHeadsUpManager.addListener(mCentralSurfacesComponent.getStatusBarHeadsUpChangeListener()); - - // Listen for demo mode changes - mDemoModeController.addCallback(mDemoModeCallback); - - if (mCommandQueueCallbacks != null) { - mCommandQueue.removeCallback(mCommandQueueCallbacks); - } - mCommandQueueCallbacks = - mCentralSurfacesComponent.getCentralSurfacesCommandQueueCallbacks(); - // Connect in to the status bar manager service - mCommandQueue.addCallback(mCommandQueueCallbacks); - - // Perform all other initialization for CentralSurfacesScope - for (CentralSurfacesComponent.Startable s : mCentralSurfacesComponent.getStartables()) { - s.start(); - } - } + boolean updateIsKeyguard(); - protected void startKeyguard() { - Trace.beginSection("CentralSurfaces#startKeyguard"); - mBiometricUnlockController = mBiometricUnlockControllerLazy.get(); - mBiometricUnlockController.setBiometricModeListener( - new BiometricUnlockController.BiometricModeListener() { - @Override - public void onResetMode() { - setWakeAndUnlocking(false); - } - - @Override - public void onModeChanged(int mode) { - switch (mode) { - case BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM: - case BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING: - case BiometricUnlockController.MODE_WAKE_AND_UNLOCK: - setWakeAndUnlocking(true); - } - } - - @Override - public void notifyBiometricAuthModeChanged() { - CentralSurfaces.this.notifyBiometricAuthModeChanged(); - } - - private void setWakeAndUnlocking(boolean wakeAndUnlocking) { - if (getNavigationBarView() != null) { - getNavigationBarView().setWakeAndUnlocking(wakeAndUnlocking); - } - } - }); - mStatusBarKeyguardViewManager.registerCentralSurfaces( - /* statusBar= */ this, - mNotificationPanelViewController, - mPanelExpansionStateManager, - mBiometricUnlockController, - mStackScroller, - mKeyguardBypassController); - mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); - mKeyguardIndicationController - .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); - mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); - mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager); - mDynamicPrivacyController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); - - mLightBarController.setBiometricUnlockController(mBiometricUnlockController); - mMediaManager.setBiometricUnlockController(mBiometricUnlockController); - mKeyguardDismissUtil.setDismissHandler(this::executeWhenUnlocked); - Trace.endSection(); - } + boolean updateIsKeyguard(boolean forceStateChange); - public NotificationShadeWindowView getNotificationShadeWindowView() { - return mNotificationShadeWindowView; - } + @NonNull + @Override + Lifecycle getLifecycle(); - public NotificationShadeWindowViewController getNotificationShadeWindowViewController() { - return mNotificationShadeWindowViewController; - } + void wakeUpIfDozing(long time, View where, String why); - public NotificationPanelViewController getNotificationPanelViewController() { - return mNotificationPanelViewController; - } + NotificationShadeWindowView getNotificationShadeWindowView(); - public ViewGroup getBouncerContainer() { - return mNotificationShadeWindowViewController.getBouncerContainer(); - } + NotificationShadeWindowViewController getNotificationShadeWindowViewController(); - public int getStatusBarHeight() { - return mStatusBarWindowController.getStatusBarHeight(); - } + NotificationPanelViewController getNotificationPanelViewController(); - /** - * Disable QS if device not provisioned. - * If the user switcher is simple then disable QS during setup because - * the user intends to use the lock screen user switcher, QS in not needed. - */ - void updateQsExpansionEnabled() { - final boolean expandEnabled = mDeviceProvisionedController.isDeviceProvisioned() - && (mUserSetup || mUserSwitcherController == null - || !mUserSwitcherController.isSimpleUserSwitcher()) - && !isShadeDisabled() - && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0) - && !mDozing - && !ONLY_CORE_APPS; - mNotificationPanelViewController.setQsExpansionEnabledPolicy(expandEnabled); - Log.d(TAG, "updateQsExpansionEnabled - QS Expand enabled: " + expandEnabled); - } + ViewGroup getBouncerContainer(); - public boolean isShadeDisabled() { - return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0; - } + int getStatusBarHeight(); - /** - * Request a notification update - * @param reason why we're requesting a notification update - */ - public void requestNotificationUpdate(String reason) { - mNotificationsController.requestNotificationUpdate(reason); - } + void updateQsExpansionEnabled(); - /** - * Asks {@link KeyguardUpdateMonitor} to run face auth. - */ - public void requestFaceAuth(boolean userInitiatedRequest) { - if (!mKeyguardStateController.canDismissLockScreen()) { - mKeyguardUpdateMonitor.requestFaceAuth(userInitiatedRequest); - } - } + boolean isShadeDisabled(); - private void updateReportRejectedTouchVisibility() { - if (mReportRejectedTouch == null) { - return; - } - mReportRejectedTouch.setVisibility(mState == StatusBarState.KEYGUARD && !mDozing - && mFalsingCollector.isReportingEnabled() ? View.VISIBLE : View.INVISIBLE); - } + void requestNotificationUpdate(String reason); - boolean areNotificationAlertsDisabled() { - return (mDisabled1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; - } + void requestFaceAuth(boolean userInitiatedRequest); @Override - public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, - int flags) { - startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, flags); - } + void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, + int flags); @Override - public void startActivity(Intent intent, boolean dismissShade) { - startActivityDismissingKeyguard(intent, false /* onlyProvisioned */, dismissShade); - } + void startActivity(Intent intent, boolean dismissShade); @Override - public void startActivity(Intent intent, boolean dismissShade, + void startActivity(Intent intent, boolean dismissShade, @Nullable ActivityLaunchAnimator.Controller animationController, - boolean showOverLockscreenWhenLocked) { - startActivity(intent, dismissShade, animationController, showOverLockscreenWhenLocked, - getActivityUserHandle(intent)); - } + boolean showOverLockscreenWhenLocked); @Override - public void startActivity(Intent intent, boolean dismissShade, + void startActivity(Intent intent, boolean dismissShade, @Nullable ActivityLaunchAnimator.Controller animationController, - boolean showOverLockscreenWhenLocked, UserHandle userHandle) { - // Make sure that we dismiss the keyguard if it is directly dismissable or when we don't - // want to show the activity above it. - if (mKeyguardStateController.isUnlocked() || !showOverLockscreenWhenLocked) { - startActivityDismissingKeyguard(intent, false, dismissShade, - false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, - 0 /* flags */, animationController, userHandle); - return; - } - - boolean animate = - animationController != null && shouldAnimateLaunch(true /* isActivityIntent */, - showOverLockscreenWhenLocked); - - ActivityLaunchAnimator.Controller controller = null; - if (animate) { - // Wrap the animation controller to dismiss the shade and set - // mIsLaunchingActivityOverLockscreen during the animation. - ActivityLaunchAnimator.Controller delegate = wrapAnimationController( - animationController, dismissShade); - controller = new DelegateLaunchAnimatorController(delegate) { - @Override - public void onIntentStarted(boolean willAnimate) { - getDelegate().onIntentStarted(willAnimate); - - if (willAnimate) { - CentralSurfaces.this.mIsLaunchingActivityOverLockscreen = true; - } - } - - @Override - public void onLaunchAnimationStart(boolean isExpandingFullyAbove) { - super.onLaunchAnimationStart(isExpandingFullyAbove); - - // Double check that the keyguard is still showing and not going away, but if so - // set the keyguard occluded. Typically, WM will let KeyguardViewMediator know - // directly, but we're overriding that to play the custom launch animation, so - // we need to take care of that here. The unocclude animation is not overridden, - // so WM will call KeyguardViewMediator's unocclude animation runner when the - // activity is exited. - if (mKeyguardStateController.isShowing() - && !mKeyguardStateController.isKeyguardGoingAway()) { - mKeyguardViewMediator.setOccluded(true /* isOccluded */, - true /* animate */); - } - } - - @Override - public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) { - // Set mIsLaunchingActivityOverLockscreen to false before actually finishing the - // animation so that we can assume that mIsLaunchingActivityOverLockscreen - // being true means that we will collapse the shade (or at least run the - // post collapse runnables) later on. - CentralSurfaces.this.mIsLaunchingActivityOverLockscreen = false; - getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove); - } - - @Override - public void onLaunchAnimationCancelled() { - // Set mIsLaunchingActivityOverLockscreen to false before actually finishing the - // animation so that we can assume that mIsLaunchingActivityOverLockscreen - // being true means that we will collapse the shade (or at least run the - // post collapse runnables) later on. - CentralSurfaces.this.mIsLaunchingActivityOverLockscreen = false; - getDelegate().onLaunchAnimationCancelled(); - } - }; - } else if (dismissShade) { - // The animation will take care of dismissing the shade at the end of the animation. If - // we don't animate, collapse it directly. - collapseShade(); - } + boolean showOverLockscreenWhenLocked, UserHandle userHandle); - mActivityLaunchAnimator.startIntentWithAnimation(controller, animate, - intent.getPackage(), showOverLockscreenWhenLocked, (adapter) -> TaskStackBuilder - .create(mContext) - .addNextIntent(intent) - .startActivities(getActivityOptions(getDisplayId(), adapter), - userHandle)); - } - - /** - * Whether we are currently animating an activity launch above the lockscreen (occluding - * activity). - */ - public boolean isLaunchingActivityOverLockscreen() { - return mIsLaunchingActivityOverLockscreen; - } + boolean isLaunchingActivityOverLockscreen(); @Override - public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade) { - startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade); - } + void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade); @Override - public void startActivity(Intent intent, boolean dismissShade, Callback callback) { - startActivityDismissingKeyguard(intent, false, dismissShade, - false /* disallowEnterPictureInPictureWhileLaunching */, callback, 0, - null /* animationController */, getActivityUserHandle(intent)); - } + void startActivity(Intent intent, boolean dismissShade, Callback callback); - public void setQsExpanded(boolean expanded) { - mNotificationShadeWindowController.setQsExpanded(expanded); - mNotificationPanelViewController.setStatusAccessibilityImportance(expanded - ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); - mNotificationPanelViewController.updateSystemUiStateFlags(); - if (getNavigationBarView() != null) { - getNavigationBarView().onStatusBarPanelStateChanged(); - } - } + void setQsExpanded(boolean expanded); - public boolean isWakeUpComingFromTouch() { - return mWakeUpComingFromTouch; - } + boolean isWakeUpComingFromTouch(); - public boolean isFalsingThresholdNeeded() { - return true; - } + boolean isFalsingThresholdNeeded(); - /** - * To be called when there's a state change in StatusBarKeyguardViewManager. - */ - public void onKeyguardViewManagerStatesUpdated() { - logStateToEventlog(); - } + void onKeyguardViewManagerStatesUpdated(); - public void setPanelExpanded(boolean isExpanded) { - if (mPanelExpanded != isExpanded) { - mNotificationLogger.onPanelExpandedChanged(isExpanded); - } - mPanelExpanded = isExpanded; - mStatusBarHideIconsForBouncerManager.setPanelExpandedAndTriggerUpdate(isExpanded); - mNotificationShadeWindowController.setPanelExpanded(isExpanded); - mStatusBarStateController.setPanelExpanded(isExpanded); - if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) { - if (DEBUG) { - Log.v(TAG, "clearing notification effects from Height"); - } - clearNotificationEffects(); - } + void setPanelExpanded(boolean isExpanded); - if (!isExpanded) { - mRemoteInputManager.onPanelCollapsed(); - } - } + ViewGroup getNotificationScrollLayout(); - public ViewGroup getNotificationScrollLayout() { - return mStackScroller; - } - - public boolean isPulsing() { - return mDozeServiceHost.isPulsing(); - } + boolean isPulsing(); @Nullable - public View getAmbientIndicationContainer() { - return mAmbientIndicationContainer; - } + View getAmbientIndicationContainer(); - /** - * When the keyguard is showing and covered by a "showWhenLocked" activity it - * is occluded. This is controlled by {@link com.android.server.policy.PhoneWindowManager} - * - * @return whether the keyguard is currently occluded - */ - public boolean isOccluded() { - return mKeyguardStateController.isOccluded(); - } + boolean isOccluded(); - /** A launch animation was cancelled. */ //TODO: These can / should probably be moved to NotificationPresenter or ShadeController - public void onLaunchAnimationCancelled(boolean isLaunchForActivity) { - if (mPresenter.isPresenterFullyCollapsed() && !mPresenter.isCollapsing() - && isLaunchForActivity) { - onClosingFinished(); - } else { - mShadeController.collapsePanel(true /* animate */); - } - } + void onLaunchAnimationCancelled(boolean isLaunchForActivity); - /** A launch animation ended. */ - public void onLaunchAnimationEnd(boolean launchIsFullScreen) { - if (!mPresenter.isCollapsing()) { - onClosingFinished(); - } - if (launchIsFullScreen) { - instantCollapseNotificationPanel(); - } - } + void onLaunchAnimationEnd(boolean launchIsFullScreen); - /** - * Whether we should animate an activity launch. - * - * Note: This method must be called *before* dismissing the keyguard. - */ - public boolean shouldAnimateLaunch(boolean isActivityIntent, boolean showOverLockscreen) { - // TODO(b/184121838): Support launch animations when occluded. - if (isOccluded()) { - return false; - } + boolean shouldAnimateLaunch(boolean isActivityIntent, boolean showOverLockscreen); - // Always animate if we are not showing the keyguard or if we animate over the lockscreen - // (without unlocking it). - if (showOverLockscreen || !mKeyguardStateController.isShowing()) { - return true; - } + boolean shouldAnimateLaunch(boolean isActivityIntent); - // If we are locked and have to dismiss the keyguard, only animate if remote unlock - // animations are enabled. We also don't animate non-activity launches as they can break the - // animation. - // TODO(b/184121838): Support non activity launches on the lockscreen. - return isActivityIntent && KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation; - } + boolean isDeviceInVrMode(); - /** Whether we should animate an activity launch. */ - public boolean shouldAnimateLaunch(boolean isActivityIntent) { - return shouldAnimateLaunch(isActivityIntent, false /* showOverLockscreen */); - } + NotificationPresenter getPresenter(); - public boolean isDeviceInVrMode() { - return mPresenter.isDeviceInVrMode(); - } + void postAnimateCollapsePanels(); - public NotificationPresenter getPresenter() { - return mPresenter; - } + void postAnimateForceCollapsePanels(); - @VisibleForTesting - void setBarStateForTest(int state) { - mState = state; - } + void postAnimateOpenPanels(); - static class KeyboardShortcutsMessage { - final int mDeviceId; + boolean isExpandedVisible(); - KeyboardShortcutsMessage(int deviceId) { - mDeviceId = deviceId; - } - } - - static class AnimateExpandSettingsPanelMessage { - final String mSubpanel; - - AnimateExpandSettingsPanelMessage(String subpanel) { - mSubpanel = subpanel; - } - } - - private void maybeEscalateHeadsUp() { - mHeadsUpManager.getAllEntries().forEach(entry -> { - final StatusBarNotification sbn = entry.getSbn(); - final Notification notification = sbn.getNotification(); - if (notification.fullScreenIntent != null) { - if (DEBUG) { - Log.d(TAG, "converting a heads up to fullScreen"); - } - try { - EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION, - sbn.getKey()); - wakeUpForFullScreenIntent(); - notification.fullScreenIntent.send(); - entry.notifyFullScreenIntentLaunched(); - } catch (PendingIntent.CanceledException e) { - } - } - }); - mHeadsUpManager.releaseAllImmediately(); - } + boolean isPanelExpanded(); - void wakeUpForFullScreenIntent() { - if (isGoingToSleep() || mDozing) { - mPowerManager.wakeUp( - SystemClock.uptimeMillis(), - PowerManager.WAKE_REASON_APPLICATION, - "com.android.systemui:full_screen_intent"); - mWakeUpComingFromTouch = false; - mWakeUpTouchLocation = null; - } - } + void onInputFocusTransfer(boolean start, boolean cancel, float velocity); - void makeExpandedVisible(boolean force) { - if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); - if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) { - return; - } + void animateCollapseQuickSettings(); - mExpandedVisible = true; + void onTouchEvent(MotionEvent event); - // Expand the window to encompass the full screen in anticipation of the drag. - // This is only possible to do atomically because the status bar is at the top of the screen! - mNotificationShadeWindowController.setPanelVisible(true); + GestureRecorder getGestureRecorder(); - visibilityChanged(true); - mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */); - setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true); - } + BiometricUnlockController getBiometricUnlockController(); - public void postAnimateCollapsePanels() { - mMainExecutor.execute(mShadeController::animateCollapsePanels); - } + void showWirelessChargingAnimation(int batteryLevel); - public void postAnimateForceCollapsePanels() { - mMainExecutor.execute( - () -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, - true /* force */)); - } - - public void postAnimateOpenPanels() { - mMessageRouter.sendMessage(MSG_OPEN_SETTINGS_PANEL); - } - - public boolean isExpandedVisible() { - return mExpandedVisible; - } - - public boolean isPanelExpanded() { - return mPanelExpanded; - } - - /** - * Called when another window is about to transfer it's input focus. - */ - public void onInputFocusTransfer(boolean start, boolean cancel, float velocity) { - if (!mCommandQueue.panelsEnabled()) { - return; - } - - if (start) { - mNotificationPanelViewController.startWaitingForOpenPanelGesture(); - } else { - mNotificationPanelViewController.stopWaitingForOpenPanelGesture(cancel, velocity); - } - } - - public void animateCollapseQuickSettings() { - if (mState == StatusBarState.SHADE) { - mNotificationPanelViewController.collapsePanel( - true, false /* delayed */, 1.0f /* speedUpFactor */); - } - } - - void makeExpandedInvisible() { - if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible - + " mExpandedVisible=" + mExpandedVisible); - - if (!mExpandedVisible || mNotificationShadeWindowView == null) { - return; - } - - // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868) - mNotificationPanelViewController.collapsePanel(/*animate=*/ false, false /* delayed*/, - 1.0f /* speedUpFactor */); - - mNotificationPanelViewController.closeQs(); - - mExpandedVisible = false; - visibilityChanged(false); - - // Update the visibility of notification shade and status bar window. - mNotificationShadeWindowController.setPanelVisible(false); - mStatusBarWindowController.setForceStatusBarVisible(false); - - // Close any guts that might be visible - mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, - true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */); - - mShadeController.runPostCollapseRunnables(); - setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); - if (!mNotificationActivityStarter.isCollapsingToShowActivityOverLockscreen()) { - showBouncerOrLockScreenIfKeyguard(); - } else if (DEBUG) { - Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen"); - } - mCommandQueue.recomputeDisableFlags( - mDisplayId, - mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */); - - // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in - // the bouncer appear animation. - if (!mStatusBarKeyguardViewManager.isShowing()) { - WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); - } - } - - /** Called when a touch event occurred on {@link PhoneStatusBarView}. */ - public void onTouchEvent(MotionEvent event) { - // TODO(b/202981994): Move this touch debugging to a central location. (Right now, it's - // split between NotificationPanelViewController and here.) - if (DEBUG_GESTURES) { - if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { - EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH, - event.getActionMasked(), (int) event.getX(), (int) event.getY(), - mDisabled1, mDisabled2); - } - - } - - if (SPEW) { - Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1=" - + mDisabled1 + " mDisabled2=" + mDisabled2); - } else if (CHATTY) { - if (event.getAction() != MotionEvent.ACTION_MOVE) { - Log.d(TAG, String.format( - "panel: %s at (%f, %f) mDisabled1=0x%08x mDisabled2=0x%08x", - MotionEvent.actionToString(event.getAction()), - event.getRawX(), event.getRawY(), mDisabled1, mDisabled2)); - } - } - - if (DEBUG_GESTURES) { - mGestureRec.add(event); - } - - if (mStatusBarWindowState == WINDOW_STATE_SHOWING) { - final boolean upOrCancel = - event.getAction() == MotionEvent.ACTION_UP || - event.getAction() == MotionEvent.ACTION_CANCEL; - setInteracting(StatusBarManager.WINDOW_STATUS_BAR, !upOrCancel || mExpandedVisible); - } - } - - public GestureRecorder getGestureRecorder() { - return mGestureRec; - } - - public BiometricUnlockController getBiometricUnlockController() { - return mBiometricUnlockController; - } - - void showTransientUnchecked() { - if (!mTransientShown) { - mTransientShown = true; - mNoAnimationOnNextBarModeChange = true; - maybeUpdateBarMode(); - } - } - - - void clearTransient() { - if (mTransientShown) { - mTransientShown = false; - maybeUpdateBarMode(); - } - } - - private void maybeUpdateBarMode() { - final int barMode = barMode(mTransientShown, mAppearance); - if (updateBarMode(barMode)) { - mLightBarController.onStatusBarModeChanged(barMode); - updateBubblesVisibility(); - } - } - - private boolean updateBarMode(int barMode) { - if (mStatusBarMode != barMode) { - mStatusBarMode = barMode; - checkBarModes(); - mAutoHideController.touchAutoHide(); - return true; - } - return false; - } - - private @TransitionMode int barMode(boolean isTransient, int appearance) { - final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_STATUS_BARS; - if (mOngoingCallController.hasOngoingCall() && mIsFullscreen) { - return MODE_SEMI_TRANSPARENT; - } else if (isTransient) { - return MODE_SEMI_TRANSPARENT; - } else if ((appearance & lightsOutOpaque) == lightsOutOpaque) { - return MODE_LIGHTS_OUT; - } else if ((appearance & APPEARANCE_LOW_PROFILE_BARS) != 0) { - return MODE_LIGHTS_OUT_TRANSPARENT; - } else if ((appearance & APPEARANCE_OPAQUE_STATUS_BARS) != 0) { - return MODE_OPAQUE; - } else if ((appearance & APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS) != 0) { - return MODE_SEMI_TRANSPARENT; - } else { - return MODE_TRANSPARENT; - } - } - - protected void showWirelessChargingAnimation(int batteryLevel) { - showChargingAnimation(batteryLevel, UNKNOWN_BATTERY_LEVEL, 0); - } - - protected void showChargingAnimation(int batteryLevel, int transmittingBatteryLevel, - long animationDelay) { - WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null, - transmittingBatteryLevel, batteryLevel, - new WirelessChargingAnimation.Callback() { - @Override - public void onAnimationStarting() { - mNotificationShadeWindowController.setRequestTopUi(true, TAG); - } - - @Override - public void onAnimationEnded() { - mNotificationShadeWindowController.setRequestTopUi(false, TAG); - } - }, false, sUiEventLogger).show(animationDelay); - } - - public void checkBarModes() { - if (mDemoModeController.isInDemoMode()) return; - if (mStatusBarTransitions != null) { - checkBarMode(mStatusBarMode, mStatusBarWindowState, mStatusBarTransitions); - } - mNavigationBarController.checkNavBarModes(mDisplayId); - mNoAnimationOnNextBarModeChange = false; - } + void checkBarModes(); // Called by NavigationBarFragment - public void setQsScrimEnabled(boolean scrimEnabled) { - mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled); - } - - /** Temporarily hides Bubbles if the status bar is hidden. */ - void updateBubblesVisibility() { - mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged( - mStatusBarMode != MODE_LIGHTS_OUT - && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT - && !mStatusBarWindowHidden)); - } - - void checkBarMode(@TransitionMode int mode, @WindowVisibleState int windowState, - BarTransitions transitions) { - final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive - && windowState != WINDOW_STATE_HIDDEN; - transitions.transitionTo(mode, anim); - } - - private void finishBarAnimations() { - if (mStatusBarTransitions != null) { - mStatusBarTransitions.finishAnimations(); - } - mNavigationBarController.finishBarAnimations(mDisplayId); - } - - private final Runnable mCheckBarModes = this::checkBarModes; + void setQsScrimEnabled(boolean scrimEnabled); - public void setInteracting(int barWindow, boolean interacting) { - mInteractingWindows = interacting - ? (mInteractingWindows | barWindow) - : (mInteractingWindows & ~barWindow); - if (mInteractingWindows != 0) { - mAutoHideController.suspendAutoHide(); - } else { - mAutoHideController.resumeSuspendedAutoHide(); - } - checkBarModes(); - } + void updateBubblesVisibility(); - private void dismissVolumeDialog() { - if (mVolumeComponent != null) { - mVolumeComponent.dismissNow(); - } - } - - public static String viewInfo(View v) { - return "[(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom() - + ") " + v.getWidth() + "x" + v.getHeight() + "]"; - } + void setInteracting(int barWindow, boolean interacting); @Override - public void dump(PrintWriter pwOriginal, String[] args) { - IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); - synchronized (mQueueLock) { - pw.println("Current Status Bar state:"); - pw.println(" mExpandedVisible=" + mExpandedVisible); - pw.println(" mDisplayMetrics=" + mDisplayMetrics); - pw.println(" mStackScroller: " + viewInfo(mStackScroller)); - pw.println(" mStackScroller: " + viewInfo(mStackScroller) - + " scroll " + mStackScroller.getScrollX() - + "," + mStackScroller.getScrollY()); - } + void dump(PrintWriter pwOriginal, String[] args); - pw.print(" mInteractingWindows="); pw.println(mInteractingWindows); - pw.print(" mStatusBarWindowState="); - pw.println(windowStateToString(mStatusBarWindowState)); - pw.print(" mStatusBarMode="); - pw.println(BarTransitions.modeToString(mStatusBarMode)); - pw.print(" mDozing="); pw.println(mDozing); - pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported); - - pw.println(" ShadeWindowView: "); - if (mNotificationShadeWindowViewController != null) { - mNotificationShadeWindowViewController.dump(pw, args); - dumpBarTransitions(pw, "PhoneStatusBarTransitions", mStatusBarTransitions); - } + void createAndAddWindows(@Nullable RegisterStatusBarResult result); - pw.println(" mMediaManager: "); - if (mMediaManager != null) { - mMediaManager.dump(pw, args); - } + float getDisplayWidth(); - pw.println(" Panels: "); - if (mNotificationPanelViewController != null) { - pw.println(" mNotificationPanel=" - + mNotificationPanelViewController.getView() + " params=" - + mNotificationPanelViewController.getView().getLayoutParams().debug("")); - pw.print (" "); - mNotificationPanelViewController.dump(pw, args); - } - pw.println(" mStackScroller: "); - if (mStackScroller != null) { - // Double indent until we rewrite the rest of this dump() - pw.increaseIndent(); - pw.increaseIndent(); - mStackScroller.dump(pw, args); - pw.decreaseIndent(); - pw.decreaseIndent(); - } - pw.println(" Theme:"); - String nightMode = mUiModeManager == null ? "null" : mUiModeManager.getNightMode() + ""; - pw.println(" dark theme: " + nightMode + - " (auto: " + UiModeManager.MODE_NIGHT_AUTO + - ", yes: " + UiModeManager.MODE_NIGHT_YES + - ", no: " + UiModeManager.MODE_NIGHT_NO + ")"); - final boolean lightWpTheme = mContext.getThemeResId() - == R.style.Theme_SystemUI_LightWallpaper; - pw.println(" light wallpaper theme: " + lightWpTheme); - - if (mKeyguardIndicationController != null) { - mKeyguardIndicationController.dump(pw, args); - } + float getDisplayHeight(); - if (mScrimController != null) { - mScrimController.dump(pw, args); - } - - if (mLightRevealScrim != null) { - pw.println( - "mLightRevealScrim.getRevealEffect(): " + mLightRevealScrim.getRevealEffect()); - pw.println( - "mLightRevealScrim.getRevealAmount(): " + mLightRevealScrim.getRevealAmount()); - } + void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned, + boolean dismissShade, int flags); - if (mStatusBarKeyguardViewManager != null) { - mStatusBarKeyguardViewManager.dump(pw); - } - - mNotificationsController.dump(pw, args, DUMPTRUCK); - - if (DEBUG_GESTURES) { - pw.print(" status bar gestures: "); - mGestureRec.dump(pw, args); - } + void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned, + boolean dismissShade); - if (mHeadsUpManager != null) { - mHeadsUpManager.dump(pw, args); - } else { - pw.println(" mHeadsUpManager: null"); - } - - if (mStatusBarTouchableRegionManager != null) { - mStatusBarTouchableRegionManager.dump(pw, args); - } else { - pw.println(" mStatusBarTouchableRegionManager: null"); - } - - if (mLightBarController != null) { - mLightBarController.dump(pw, args); - } - - pw.println("SharedPreferences:"); - for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) { - pw.print(" "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue()); - } - - pw.println("Camera gesture intents:"); - pw.println(" Insecure camera: " + CameraIntents.getInsecureCameraIntent(mContext)); - pw.println(" Secure camera: " + CameraIntents.getSecureCameraIntent(mContext)); - pw.println(" Override package: " - + CameraIntents.getOverrideCameraPackage(mContext)); - } - - public static void dumpBarTransitions( - PrintWriter pw, String var, @Nullable BarTransitions transitions) { - pw.print(" "); pw.print(var); pw.print(".BarTransitions.mMode="); - if (transitions != null) { - pw.println(BarTransitions.modeToString(transitions.getMode())); - } else { - pw.println("Unknown"); - } - } - - public void createAndAddWindows(@Nullable RegisterStatusBarResult result) { - makeStatusBarView(result); - mNotificationShadeWindowController.attach(); - mStatusBarWindowController.attach(); - } - - // called by makeStatusbar and also by PhoneStatusBarView - void updateDisplaySize() { - mDisplay.getMetrics(mDisplayMetrics); - mDisplay.getSize(mCurrentDisplaySize); - if (DEBUG_GESTURES) { - mGestureRec.tag("display", - String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); - } - } - - float getDisplayDensity() { - return mDisplayMetrics.density; - } - - public float getDisplayWidth() { - return mDisplayMetrics.widthPixels; - } - - public float getDisplayHeight() { - return mDisplayMetrics.heightPixels; - } - - int getRotation() { - return mDisplay.getRotation(); - } - - int getDisplayId() { - return mDisplayId; - } - - public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, - boolean dismissShade, int flags) { - startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, - false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, - flags, null /* animationController */, getActivityUserHandle(intent)); - } - - public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, - boolean dismissShade) { - startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, 0); - } - - void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, - final boolean dismissShade, final boolean disallowEnterPictureInPictureWhileLaunching, - final Callback callback, int flags, + void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned, + boolean dismissShade, boolean disallowEnterPictureInPictureWhileLaunching, + Callback callback, int flags, @Nullable ActivityLaunchAnimator.Controller animationController, - final UserHandle userHandle) { - if (onlyProvisioned && !mDeviceProvisionedController.isDeviceProvisioned()) return; - - final boolean willLaunchResolverActivity = - mActivityIntentHelper.wouldLaunchResolverActivity(intent, - mLockscreenUserManager.getCurrentUserId()); - - boolean animate = - animationController != null && !willLaunchResolverActivity && shouldAnimateLaunch( - true /* isActivityIntent */); - ActivityLaunchAnimator.Controller animController = - animationController != null ? wrapAnimationController(animationController, - dismissShade) : null; - - // If we animate, we will dismiss the shade only once the animation is done. This is taken - // care of by the StatusBarLaunchAnimationController. - boolean dismissShadeDirectly = dismissShade && animController == null; - - Runnable runnable = () -> { - mAssistManagerLazy.get().hideAssist(); - intent.setFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.addFlags(flags); - int[] result = new int[]{ActivityManager.START_CANCELED}; - - mActivityLaunchAnimator.startIntentWithAnimation(animController, - animate, intent.getPackage(), (adapter) -> { - ActivityOptions options = new ActivityOptions( - getActivityOptions(mDisplayId, adapter)); - options.setDisallowEnterPictureInPictureWhileLaunching( - disallowEnterPictureInPictureWhileLaunching); - if (CameraIntents.isInsecureCameraIntent(intent)) { - // Normally an activity will set it's requested rotation - // animation on its window. However when launching an activity - // causes the orientation to change this is too late. In these cases - // the default animation is used. This doesn't look good for - // the camera (as it rotates the camera contents out of sync - // with physical reality). So, we ask the WindowManager to - // force the crossfade animation if an orientation change - // happens to occur during the launch. - options.setRotationAnimationHint( - WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS); - } - if (Settings.Panel.ACTION_VOLUME.equals(intent.getAction())) { - // Settings Panel is implemented as activity(not a dialog), so - // underlying app is paused and may enter picture-in-picture mode - // as a result. - // So we need to disable picture-in-picture mode here - // if it is volume panel. - options.setDisallowEnterPictureInPictureWhileLaunching(true); - } - - try { - result[0] = ActivityTaskManager.getService().startActivityAsUser( - null, mContext.getBasePackageName(), - mContext.getAttributionTag(), - intent, - intent.resolveTypeIfNeeded(mContext.getContentResolver()), - null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, - options.toBundle(), userHandle.getIdentifier()); - } catch (RemoteException e) { - Log.w(TAG, "Unable to start activity", e); - } - return result[0]; - }); - - if (callback != null) { - callback.onActivityStarted(result[0]); - } - }; - Runnable cancelRunnable = () -> { - if (callback != null) { - callback.onActivityStarted(ActivityManager.START_CANCELED); - } - }; - executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShadeDirectly, - willLaunchResolverActivity, true /* deferred */, animate); - } + UserHandle userHandle); - @Nullable - private ActivityLaunchAnimator.Controller wrapAnimationController( - ActivityLaunchAnimator.Controller animationController, boolean dismissShade) { - View rootView = animationController.getLaunchContainer().getRootView(); - - Optional<ActivityLaunchAnimator.Controller> controllerFromStatusBar = - mStatusBarWindowController.wrapAnimationControllerIfInStatusBar( - rootView, animationController); - if (controllerFromStatusBar.isPresent()) { - return controllerFromStatusBar.get(); - } - - if (dismissShade) { - // If the view is not in the status bar, then we are animating a view in the shade. - // We have to make sure that we collapse it when the animation ends or is cancelled. - return new StatusBarLaunchAnimatorController(animationController, this, - true /* isLaunchForActivity */); - } + void readyForKeyguardDone(); - return animationController; - } + void executeRunnableDismissingKeyguard(Runnable runnable, + Runnable cancelAction, + boolean dismissShade, + boolean afterKeyguardGone, + boolean deferred); - public void readyForKeyguardDone() { - mStatusBarKeyguardViewManager.readyForKeyguardDone(); - } + void executeRunnableDismissingKeyguard(Runnable runnable, + Runnable cancelAction, + boolean dismissShade, + boolean afterKeyguardGone, + boolean deferred, + boolean willAnimateOnKeyguard); - public void executeRunnableDismissingKeyguard(final Runnable runnable, - final Runnable cancelAction, - final boolean dismissShade, - final boolean afterKeyguardGone, - final boolean deferred) { - executeRunnableDismissingKeyguard(runnable, cancelAction, dismissShade, afterKeyguardGone, - deferred, false /* willAnimateOnKeyguard */); - } - - public void executeRunnableDismissingKeyguard(final Runnable runnable, - final Runnable cancelAction, - final boolean dismissShade, - final boolean afterKeyguardGone, - final boolean deferred, - final boolean willAnimateOnKeyguard) { - OnDismissAction onDismissAction = new OnDismissAction() { - @Override - public boolean onDismiss() { - if (runnable != null) { - if (mStatusBarKeyguardViewManager.isShowing() - && mStatusBarKeyguardViewManager.isOccluded()) { - mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); - } else { - mMainExecutor.execute(runnable); - } - } - if (dismissShade) { - if (mExpandedVisible && !mBouncerShowing) { - mShadeController.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */, true /* delayed*/); - } else { - - // Do it after DismissAction has been processed to conserve the needed - // ordering. - mMainExecutor.execute(mShadeController::runPostCollapseRunnables); - } - } else if (CentralSurfaces.this.isInLaunchTransition() - && mNotificationPanelViewController.isLaunchTransitionFinished()) { - - // We are not dismissing the shade, but the launch transition is already - // finished, - // so nobody will call readyForKeyguardDone anymore. Post it such that - // keyguardDonePending gets called first. - mMainExecutor.execute(mStatusBarKeyguardViewManager::readyForKeyguardDone); - } - return deferred; - } - - @Override - public boolean willRunAnimationOnKeyguard() { - return willAnimateOnKeyguard; - } - }; - dismissKeyguardThenExecute(onDismissAction, cancelAction, afterKeyguardGone); - } - - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - Trace.beginSection("CentralSurfaces#onReceive"); - if (DEBUG) Log.v(TAG, "onReceive: " + intent); - String action = intent.getAction(); - String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); - if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { - KeyboardShortcuts.dismiss(); - mRemoteInputManager.closeRemoteInputs(); - if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) { - int flags = CommandQueue.FLAG_EXCLUDE_NONE; - if (reason != null) { - if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) { - flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL; - } - // Do not collapse notifications when starting dreaming if the notifications - // shade is used for the screen off animation. It might require expanded - // state for the scrims to be visible - if (reason.equals(SYSTEM_DIALOG_REASON_DREAM) - && mScreenOffAnimationController.shouldExpandNotifications()) { - flags |= CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL; - } - } - mShadeController.animateCollapsePanels(flags); - } - } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { - if (mNotificationShadeWindowController != null) { - mNotificationShadeWindowController.setNotTouchable(false); - } - finishBarAnimations(); - resetUserExpandedStates(); - } - Trace.endSection(); - } - }; - - private final BroadcastReceiver mDemoReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (DEBUG) Log.v(TAG, "onReceive: " + intent); - String action = intent.getAction(); - if (ACTION_FAKE_ARTWORK.equals(action)) { - if (DEBUG_MEDIA_FAKE_ARTWORK) { - mPresenter.updateMediaMetaData(true, true); - } - } - } - }; - - public void resetUserExpandedStates() { - mNotificationsController.resetUserExpandedStates(); - } - - private void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen, - boolean afterKeyguardGone) { - if (mStatusBarKeyguardViewManager.isShowing() && requiresShadeOpen) { - mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); - } - dismissKeyguardThenExecute(action, null /* cancelAction */, - afterKeyguardGone /* afterKeyguardGone */); - } - - protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) { - dismissKeyguardThenExecute(action, null /* cancelRunnable */, afterKeyguardGone); - } + void resetUserExpandedStates(); @Override - public void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction, - boolean afterKeyguardGone) { - if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP - && mKeyguardStateController.canDismissLockScreen() - && !mStatusBarStateController.leaveOpenOnKeyguardHide() - && mDozeServiceHost.isPulsing()) { - // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a pulse. - // TODO: Factor this transition out of BiometricUnlockController. - mBiometricUnlockController.startWakeAndUnlock( - BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING); - } - if (mStatusBarKeyguardViewManager.isShowing()) { - mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction, - afterKeyguardGone); - } else { - action.onDismiss(); - } - } - /** - * Notify the shade controller that the current user changed - * - * @param newUserId userId of the new user - */ - public void setLockscreenUser(int newUserId) { - if (mLockscreenWallpaper != null) { - mLockscreenWallpaper.setCurrentUser(newUserId); - } - mScrimController.setCurrentUser(newUserId); - if (mWallpaperSupported) { - mWallpaperChangedReceiver.onReceive(mContext, null); - } - } + void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction, + boolean afterKeyguardGone); - /** - * Reload some of our resources when the configuration changes. - * - * We don't reload everything when the configuration changes -- we probably - * should, but getting that smooth is tough. Someday we'll fix that. In the - * meantime, just update the things that we know change. - */ - void updateResources() { - // Update the quick setting tiles - if (mQSPanelController != null) { - mQSPanelController.updateResources(); - } - - if (mStatusBarWindowController != null) { - mStatusBarWindowController.refreshStatusBarHeight(); - } - - if (mNotificationPanelViewController != null) { - mNotificationPanelViewController.updateResources(); - } - if (mBrightnessMirrorController != null) { - mBrightnessMirrorController.updateResources(); - } - if (mStatusBarKeyguardViewManager != null) { - mStatusBarKeyguardViewManager.updateResources(); - } - - mPowerButtonReveal = new PowerButtonReveal(mContext.getResources().getDimensionPixelSize( - com.android.systemui.R.dimen.physical_power_button_center_screen_location_y)); - } - - // Visibility reporting - protected void handleVisibleToUserChanged(boolean visibleToUser) { - if (visibleToUser) { - handleVisibleToUserChangedImpl(visibleToUser); - mNotificationLogger.startNotificationLogging(); - } else { - mNotificationLogger.stopNotificationLogging(); - handleVisibleToUserChangedImpl(visibleToUser); - } - } - - // Visibility reporting - void handleVisibleToUserChangedImpl(boolean visibleToUser) { - if (visibleToUser) { - /* The LEDs are turned off when the notification panel is shown, even just a little bit. - * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do - * this. - */ - boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp(); - boolean clearNotificationEffects = - !mPresenter.isPresenterFullyCollapsed() && - (mState == StatusBarState.SHADE - || mState == StatusBarState.SHADE_LOCKED); - int notificationLoad = mNotificationsController.getActiveNotificationsCount(); - if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) { - notificationLoad = 1; - } - final int finalNotificationLoad = notificationLoad; - mUiBgExecutor.execute(() -> { - try { - mBarService.onPanelRevealed(clearNotificationEffects, - finalNotificationLoad); - } catch (RemoteException ex) { - // Won't fail unless the world has ended. - } - }); - } else { - mUiBgExecutor.execute(() -> { - try { - mBarService.onPanelHidden(); - } catch (RemoteException ex) { - // Won't fail unless the world has ended. - } - }); - } - - } - - private void logStateToEventlog() { - boolean isShowing = mStatusBarKeyguardViewManager.isShowing(); - boolean isOccluded = mStatusBarKeyguardViewManager.isOccluded(); - boolean isBouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing(); - boolean isSecure = mKeyguardStateController.isMethodSecure(); - boolean unlocked = mKeyguardStateController.canDismissLockScreen(); - int stateFingerprint = getLoggingFingerprint(mState, - isShowing, - isOccluded, - isBouncerShowing, - isSecure, - unlocked); - if (stateFingerprint != mLastLoggedStateFingerprint) { - if (mStatusBarStateLog == null) { - mStatusBarStateLog = new LogMaker(MetricsEvent.VIEW_UNKNOWN); - } - mMetricsLogger.write(mStatusBarStateLog - .setCategory(isBouncerShowing ? MetricsEvent.BOUNCER : MetricsEvent.LOCKSCREEN) - .setType(isShowing ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE) - .setSubtype(isSecure ? 1 : 0)); - EventLogTags.writeSysuiStatusBarState(mState, - isShowing ? 1 : 0, - isOccluded ? 1 : 0, - isBouncerShowing ? 1 : 0, - isSecure ? 1 : 0, - unlocked ? 1 : 0); - mLastLoggedStateFingerprint = stateFingerprint; - - StringBuilder uiEventValueBuilder = new StringBuilder(); - uiEventValueBuilder.append(isBouncerShowing ? "BOUNCER" : "LOCKSCREEN"); - uiEventValueBuilder.append(isShowing ? "_OPEN" : "_CLOSE"); - uiEventValueBuilder.append(isSecure ? "_SECURE" : "_INSECURE"); - sUiEventLogger.log(StatusBarUiEvent.valueOf(uiEventValueBuilder.toString())); - } - } - - /** - * Returns a fingerprint of fields logged to eventlog - */ - private static int getLoggingFingerprint(int statusBarState, boolean keyguardShowing, - boolean keyguardOccluded, boolean bouncerShowing, boolean secure, - boolean currentlyInsecure) { - // Reserve 8 bits for statusBarState. We'll never go higher than - // that, right? Riiiight. - return (statusBarState & 0xFF) - | ((keyguardShowing ? 1 : 0) << 8) - | ((keyguardOccluded ? 1 : 0) << 9) - | ((bouncerShowing ? 1 : 0) << 10) - | ((secure ? 1 : 0) << 11) - | ((currentlyInsecure ? 1 : 0) << 12); - } + void setLockscreenUser(int newUserId); @Override - public void postQSRunnableDismissingKeyguard(final Runnable runnable) { - mMainExecutor.execute(() -> { - mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); - executeRunnableDismissingKeyguard( - () -> mMainExecutor.execute(runnable), null, false, false, false); - }); - } + void postQSRunnableDismissingKeyguard(Runnable runnable); @Override - public void postStartActivityDismissingKeyguard(PendingIntent intent) { - postStartActivityDismissingKeyguard(intent, null /* animationController */); - } + void postStartActivityDismissingKeyguard(PendingIntent intent); @Override - public void postStartActivityDismissingKeyguard(final PendingIntent intent, - @Nullable ActivityLaunchAnimator.Controller animationController) { - mMainExecutor.execute(() -> startPendingIntentDismissingKeyguard(intent, - null /* intentSentUiThreadCallback */, animationController)); - } + void postStartActivityDismissingKeyguard(PendingIntent intent, + @Nullable ActivityLaunchAnimator.Controller animationController); @Override - public void postStartActivityDismissingKeyguard(final Intent intent, int delay) { - postStartActivityDismissingKeyguard(intent, delay, null /* animationController */); - } + void postStartActivityDismissingKeyguard(Intent intent, int delay); @Override - public void postStartActivityDismissingKeyguard(Intent intent, int delay, - @Nullable ActivityLaunchAnimator.Controller animationController) { - mMainExecutor.executeDelayed( - () -> - startActivityDismissingKeyguard(intent, true /* onlyProvisioned */, - true /* dismissShade */, - false /* disallowEnterPictureInPictureWhileLaunching */, - null /* callback */, - 0 /* flags */, - animationController, - getActivityUserHandle(intent)), - delay); - } + void postStartActivityDismissingKeyguard(Intent intent, int delay, + @Nullable ActivityLaunchAnimator.Controller animationController); - public void showKeyguard() { - mStatusBarStateController.setKeyguardRequested(true); - mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); - updateIsKeyguard(); - mAssistManagerLazy.get().onLockscreenShown(); - } + void showKeyguard(); - public boolean hideKeyguard() { - mStatusBarStateController.setKeyguardRequested(false); - return updateIsKeyguard(); - } + boolean hideKeyguard(); - boolean updateIsKeyguard() { - return updateIsKeyguard(false /* forceStateChange */); - } + void showKeyguardImpl(); - boolean updateIsKeyguard(boolean forceStateChange) { - boolean wakeAndUnlocking = mBiometricUnlockController.isWakeAndUnlock(); - - // For dozing, keyguard needs to be shown whenever the device is non-interactive. Otherwise - // there's no surface we can show to the user. Note that the device goes fully interactive - // late in the transition, so we also allow the device to start dozing once the screen has - // turned off fully. - boolean keyguardForDozing = mDozeServiceHost.getDozingRequested() - && (!mDeviceInteractive || (isGoingToSleep() - && (isScreenFullyOff() - || (mKeyguardStateController.isShowing() && !isOccluded())))); - boolean isWakingAndOccluded = isOccluded() && isWakingOrAwake(); - boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested() - || keyguardForDozing) && !wakeAndUnlocking && !isWakingAndOccluded; - if (keyguardForDozing) { - updatePanelExpansionForKeyguard(); - } - if (shouldBeKeyguard) { - if (mScreenOffAnimationController.isKeyguardShowDelayed() - || (isGoingToSleep() - && mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_TURNING_OFF)) { - // Delay showing the keyguard until screen turned off. - } else { - showKeyguardImpl(); - } - } else { - // During folding a foldable device this might be called as a result of - // 'onScreenTurnedOff' call for the inner display. - // In this case: - // * When phone is locked on folding: it doesn't make sense to hide keyguard as it - // will be immediately locked again - // * When phone is unlocked: we still don't want to execute hiding of the keyguard - // as the animation could prepare 'fake AOD' interface (without actually - // transitioning to keyguard state) and this might reset the view states - if (!mScreenOffAnimationController.isKeyguardHideDelayed()) { - return hideKeyguardImpl(forceStateChange); - } - } - return false; - } + boolean isInLaunchTransition(); - public void showKeyguardImpl() { - Trace.beginSection("CentralSurfaces#showKeyguard"); - // In case we're locking while a smartspace transition is in progress, reset it. - mKeyguardUnlockAnimationController.resetSmartspaceTransition(); - if (mKeyguardStateController.isLaunchTransitionFadingAway()) { - mNotificationPanelViewController.cancelAnimation(); - onLaunchTransitionFadingEnded(); - } - mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); - if (!mLockscreenShadeTransitionController.isWakingToShadeLocked()) { - mStatusBarStateController.setState(StatusBarState.KEYGUARD); - } - updatePanelExpansionForKeyguard(); - Trace.endSection(); - } + void fadeKeyguardAfterLaunchTransition(Runnable beforeFading, + Runnable endRunnable, Runnable cancelRunnable); - private void updatePanelExpansionForKeyguard() { - if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode() - != BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) { - mShadeController.instantExpandNotificationsPanel(); - } - } + void fadeKeyguardWhilePulsing(); - private void onLaunchTransitionFadingEnded() { - mNotificationPanelViewController.resetAlpha(); - mNotificationPanelViewController.onAffordanceLaunchEnded(); - releaseGestureWakeLock(); - runLaunchTransitionEndRunnable(); - mKeyguardStateController.setLaunchTransitionFadingAway(false); - mPresenter.updateMediaMetaData(true /* metaDataChanged */, true); - } + void animateKeyguardUnoccluding(); - public boolean isInLaunchTransition() { - return mNotificationPanelViewController.isLaunchTransitionRunning() - || mNotificationPanelViewController.isLaunchTransitionFinished(); - } + void startLaunchTransitionTimeout(); - /** - * Fades the content of the keyguard away after the launch transition is done. - * - * @param beforeFading the runnable to be run when the circle is fully expanded and the fading - * starts - * @param endRunnable the runnable to be run when the transition is done. Will not run - * if the transition is cancelled, instead cancelRunnable will run - * @param cancelRunnable the runnable to be run if the transition is cancelled - */ - public void fadeKeyguardAfterLaunchTransition(final Runnable beforeFading, - Runnable endRunnable, Runnable cancelRunnable) { - mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); - mLaunchTransitionEndRunnable = endRunnable; - mLaunchTransitionCancelRunnable = cancelRunnable; - Runnable hideRunnable = () -> { - mKeyguardStateController.setLaunchTransitionFadingAway(true); - if (beforeFading != null) { - beforeFading.run(); - } - updateScrimController(); - mPresenter.updateMediaMetaData(false, true); - mNotificationPanelViewController.resetAlpha(); - mNotificationPanelViewController.fadeOut( - FADE_KEYGUARD_START_DELAY, FADE_KEYGUARD_DURATION, - this::onLaunchTransitionFadingEnded); - mCommandQueue.appTransitionStarting(mDisplayId, SystemClock.uptimeMillis(), - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true); - }; - if (mNotificationPanelViewController.isLaunchTransitionRunning()) { - mNotificationPanelViewController.setLaunchTransitionEndRunnable(hideRunnable); - } else { - hideRunnable.run(); - } - } + boolean hideKeyguardImpl(boolean forceStateChange); - private void cancelAfterLaunchTransitionRunnables() { - if (mLaunchTransitionCancelRunnable != null) { - mLaunchTransitionCancelRunnable.run(); - } - mLaunchTransitionEndRunnable = null; - mLaunchTransitionCancelRunnable = null; - mNotificationPanelViewController.setLaunchTransitionEndRunnable(null); - } + void keyguardGoingAway(); - /** - * Fades the content of the Keyguard while we are dozing and makes it invisible when finished - * fading. - */ - public void fadeKeyguardWhilePulsing() { - mNotificationPanelViewController.fadeOut(0, FADE_KEYGUARD_DURATION_PULSING, - ()-> { - hideKeyguard(); - mStatusBarKeyguardViewManager.onKeyguardFadedAway(); - }).start(); - } + void setKeyguardFadingAway(long startTime, long delay, long fadeoutDuration, + boolean isBypassFading); - /** - * Plays the animation when an activity that was occluding Keyguard goes away. - */ - public void animateKeyguardUnoccluding() { - mNotificationPanelViewController.setExpandedFraction(0f); - mCommandQueueCallbacks.animateExpandNotificationsPanel(); - mScrimController.setUnocclusionAnimationRunning(true); - } + void finishKeyguardFadingAway(); - /** - * Starts the timeout when we try to start the affordances on Keyguard. We usually rely that - * Keyguard goes away via fadeKeyguardAfterLaunchTransition, however, that might not happen - * because the launched app crashed or something else went wrong. - */ - public void startLaunchTransitionTimeout() { - mMessageRouter.sendMessageDelayed( - MSG_LAUNCH_TRANSITION_TIMEOUT, LAUNCH_TRANSITION_TIMEOUT_MS); - } + void userActivity(); - private void onLaunchTransitionTimeout() { - Log.w(TAG, "Launch transition: Timeout!"); - mNotificationPanelViewController.onAffordanceLaunchEnded(); - releaseGestureWakeLock(); - mNotificationPanelViewController.resetViews(false /* animate */); - } + boolean interceptMediaKey(KeyEvent event); - private void runLaunchTransitionEndRunnable() { - mLaunchTransitionCancelRunnable = null; - if (mLaunchTransitionEndRunnable != null) { - Runnable r = mLaunchTransitionEndRunnable; + boolean dispatchKeyEventPreIme(KeyEvent event); - // mLaunchTransitionEndRunnable might call showKeyguard, which would execute it again, - // which would lead to infinite recursion. Protect against it. - mLaunchTransitionEndRunnable = null; - r.run(); - } - } + boolean onMenuPressed(); - /** - * @return true if we would like to stay in the shade, false if it should go away entirely - */ - public boolean hideKeyguardImpl(boolean forceStateChange) { - Trace.beginSection("CentralSurfaces#hideKeyguard"); - boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide(); - int previousState = mStatusBarStateController.getState(); - if (!(mStatusBarStateController.setState(StatusBarState.SHADE, forceStateChange))) { - //TODO: StatusBarStateController should probably know about hiding the keyguard and - // notify listeners. - - // If the state didn't change, we may still need to update public mode - mLockscreenUserManager.updatePublicMode(); - } - if (mStatusBarStateController.leaveOpenOnKeyguardHide()) { - if (!mStatusBarStateController.isKeyguardRequested()) { - mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); - } - long delay = mKeyguardStateController.calculateGoingToFullShadeDelay(); - mLockscreenShadeTransitionController.onHideKeyguard(delay, previousState); - - // Disable layout transitions in navbar for this transition because the load is just - // too heavy for the CPU and GPU on any device. - mNavigationBarController.disableAnimationsDuringHide(mDisplayId, delay); - } else if (!mNotificationPanelViewController.isCollapsing()) { - instantCollapseNotificationPanel(); - } + void endAffordanceLaunch(); - // Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile - // visibilities so next time we open the panel we know the correct height already. - if (mQSPanelController != null) { - mQSPanelController.refreshAllTiles(); - } - mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); - releaseGestureWakeLock(); - mNotificationPanelViewController.onAffordanceLaunchEnded(); - mNotificationPanelViewController.resetAlpha(); - mNotificationPanelViewController.resetTranslation(); - mNotificationPanelViewController.resetViewGroupFade(); - updateDozingState(); - updateScrimController(); - Trace.endSection(); - return staying; - } + boolean onBackPressed(); - private void releaseGestureWakeLock() { - if (mGestureWakeLock.isHeld()) { - mGestureWakeLock.release(); - } - } - - /** - * Notifies the status bar that Keyguard is going away very soon. - */ - public void keyguardGoingAway() { - // Treat Keyguard exit animation as an app transition to achieve nice transition for status - // bar. - mKeyguardStateController.notifyKeyguardGoingAway(true); - mCommandQueue.appTransitionPending(mDisplayId, true /* forced */); - updateScrimController(); - } - - /** - * Notifies the status bar the Keyguard is fading away with the specified timings. - * @param startTime the start time of the animations in uptime millis - * @param delay the precalculated animation delay in milliseconds - * @param fadeoutDuration the duration of the exit animation, in milliseconds - * @param isBypassFading is this a fading away animation while bypassing - */ - public void setKeyguardFadingAway(long startTime, long delay, long fadeoutDuration, - boolean isBypassFading) { - mCommandQueue.appTransitionStarting(mDisplayId, startTime + fadeoutDuration - - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true); - mCommandQueue.recomputeDisableFlags(mDisplayId, fadeoutDuration > 0 /* animate */); - mCommandQueue.appTransitionStarting(mDisplayId, - startTime - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true); - mKeyguardStateController.notifyKeyguardFadingAway(delay, fadeoutDuration, isBypassFading); - } - - /** - * Notifies that the Keyguard fading away animation is done. - */ - public void finishKeyguardFadingAway() { - mKeyguardStateController.notifyKeyguardDoneFading(); - mScrimController.setExpansionAffectsAlpha(true); - - // If the device was re-locked while unlocking, we might have a pending lock that was - // delayed because the keyguard was in the middle of going away. - mKeyguardViewMediator.maybeHandlePendingLock(); - } - - /** - * Switches theme from light to dark and vice-versa. - */ - protected void updateTheme() { - // Set additional scrim only if the lock and system wallpaper are different to prevent - // applying the dimming effect twice. - mUiBgExecutor.execute(() -> { - float dimAmount = 0f; - if (mWallpaperManager.lockScreenWallpaperExists()) { - dimAmount = mWallpaperManager.getWallpaperDimAmount(); - } - final float scrimDimAmount = dimAmount; - mMainExecutor.execute(() -> { - mScrimController.setAdditionalScrimBehindAlphaKeyguard(scrimDimAmount); - mScrimController.applyCompositeAlphaOnScrimBehindKeyguard(); - }); - }); - - // Lock wallpaper defines the color of the majority of the views, hence we'll use it - // to set our default theme. - final boolean lockDarkText = mColorExtractor.getNeutralColors().supportsDarkText(); - final int themeResId = lockDarkText ? R.style.Theme_SystemUI_LightWallpaper - : R.style.Theme_SystemUI; - if (mContext.getThemeResId() != themeResId) { - mContext.setTheme(themeResId); - mConfigurationController.notifyThemeChanged(); - } - } - - private void updateDozingState() { - Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0); - Trace.beginSection("CentralSurfaces#updateDozingState"); - - boolean visibleNotOccluded = mStatusBarKeyguardViewManager.isShowing() - && !mStatusBarKeyguardViewManager.isOccluded(); - // If we're dozing and we'll be animating the screen off, the keyguard isn't currently - // visible but will be shortly for the animation, so we should proceed as if it's visible. - boolean visibleNotOccludedOrWillBe = - visibleNotOccluded || (mDozing && mDozeParameters.shouldDelayKeyguardShow()); - - boolean wakeAndUnlock = mBiometricUnlockController.getMode() - == BiometricUnlockController.MODE_WAKE_AND_UNLOCK; - boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock) - || (mDozing && mDozeParameters.shouldControlScreenOff() - && visibleNotOccludedOrWillBe); - - mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation); - updateQsExpansionEnabled(); - Trace.endSection(); - } - - public void userActivity() { - if (mState == StatusBarState.KEYGUARD) { - mKeyguardViewMediatorCallback.userActivity(); - } - } - - public boolean interceptMediaKey(KeyEvent event) { - return mState == StatusBarState.KEYGUARD - && mStatusBarKeyguardViewManager.interceptMediaKey(event); - } - - /** - * While IME is active and a BACK event is detected, check with - * {@link StatusBarKeyguardViewManager#dispatchBackKeyEventPreIme()} to see if the event - * should be handled before routing to IME, in order to prevent the user having to hit back - * twice to exit bouncer. - */ - public boolean dispatchKeyEventPreIme(KeyEvent event) { - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_BACK: - if (mState == StatusBarState.KEYGUARD - && mStatusBarKeyguardViewManager.dispatchBackKeyEventPreIme()) { - return onBackPressed(); - } - } - return false; - } - - protected boolean shouldUnlockOnMenuPressed() { - return mDeviceInteractive && mState != StatusBarState.SHADE - && mStatusBarKeyguardViewManager.shouldDismissOnMenuPressed(); - } - - public boolean onMenuPressed() { - if (shouldUnlockOnMenuPressed()) { - mShadeController.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */); - return true; - } - return false; - } - - public void endAffordanceLaunch() { - releaseGestureWakeLock(); - mNotificationPanelViewController.onAffordanceLaunchEnded(); - } - - public boolean onBackPressed() { - boolean isScrimmedBouncer = mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED; - if (mStatusBarKeyguardViewManager.onBackPressed(isScrimmedBouncer /* hideImmediately */)) { - if (isScrimmedBouncer) { - mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); - } else { - mNotificationPanelViewController.expandWithoutQs(); - } - return true; - } - if (mNotificationPanelViewController.isQsCustomizing()) { - mNotificationPanelViewController.closeQsCustomizer(); - return true; - } - if (mNotificationPanelViewController.isQsExpanded()) { - if (mNotificationPanelViewController.isQsDetailShowing()) { - mNotificationPanelViewController.closeQsDetail(); - } else { - mNotificationPanelViewController.animateCloseQs(false /* animateAway */); - } - return true; - } - if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) { - return true; - } - if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) { - if (mNotificationPanelViewController.canPanelBeCollapsed()) { - mShadeController.animateCollapsePanels(); - } - return true; - } - return false; - } - - public boolean onSpacePressed() { - if (mDeviceInteractive && mState != StatusBarState.SHADE) { - mShadeController.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */); - return true; - } - return false; - } - - private void showBouncerOrLockScreenIfKeyguard() { - // If the keyguard is animating away, we aren't really the keyguard anymore and should not - // show the bouncer/lockscreen. - if (!mKeyguardViewMediator.isHiding() - && !mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) { - if (mState == StatusBarState.SHADE_LOCKED - && mKeyguardUpdateMonitor.isUdfpsEnrolled()) { - // shade is showing while locked on the keyguard, so go back to showing the - // lock screen where users can use the UDFPS affordance to enter the device - mStatusBarKeyguardViewManager.reset(true); - } else if ((mState == StatusBarState.KEYGUARD - && !mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()) - || mState == StatusBarState.SHADE_LOCKED) { - mStatusBarKeyguardViewManager.showGenericBouncer(true /* scrimmed */); - } - } - } + boolean onSpacePressed(); - /** - * Show the bouncer if we're currently on the keyguard or shade locked and aren't hiding. - * @param performAction the action to perform when the bouncer is dismissed. - * @param cancelAction the action to perform when unlock is aborted. - */ - public void showBouncerWithDimissAndCancelIfKeyguard(OnDismissAction performAction, - Runnable cancelAction) { - if ((mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) - && !mKeyguardViewMediator.isHiding()) { - mStatusBarKeyguardViewManager.dismissWithAction(performAction, cancelAction, - false /* afterKeyguardGone */); - } else if (cancelAction != null) { - cancelAction.run(); - } - } + void showBouncerWithDimissAndCancelIfKeyguard(OnDismissAction performAction, + Runnable cancelAction); - void instantCollapseNotificationPanel() { - mNotificationPanelViewController.instantCollapse(); - mShadeController.runPostCollapseRunnables(); - } + LightRevealScrim getLightRevealScrim(); - /** - * Collapse the panel directly if we are on the main thread, post the collapsing on the main - * thread if we are not. - */ - void collapsePanelOnMainThread() { - if (Looper.getMainLooper().isCurrentThread()) { - mShadeController.collapsePanel(); - } else { - mContext.getMainExecutor().execute(mShadeController::collapsePanel); - } - } + void onTrackingStarted(); - /** Collapse the panel. The collapsing will be animated for the given {@code duration}. */ - void collapsePanelWithDuration(int duration) { - mNotificationPanelViewController.collapseWithDuration(duration); - } + void onClosingFinished(); - /** - * Updates the light reveal effect to reflect the reason we're waking or sleeping (for example, - * from the power button). - * @param wakingUp Whether we're updating because we're waking up (true) or going to sleep - * (false). - */ - private void updateRevealEffect(boolean wakingUp) { - if (mLightRevealScrim == null) { - return; - } + void onUnlockHintStarted(); - final boolean wakingUpFromPowerButton = wakingUp - && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal) - && mWakefulnessLifecycle.getLastWakeReason() - == PowerManager.WAKE_REASON_POWER_BUTTON; - final boolean sleepingFromPowerButton = !wakingUp - && mWakefulnessLifecycle.getLastSleepReason() - == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON; - - if (wakingUpFromPowerButton || sleepingFromPowerButton) { - mLightRevealScrim.setRevealEffect(mPowerButtonReveal); - mLightRevealScrim.setRevealAmount(1f - mStatusBarStateController.getDozeAmount()); - } else if (!wakingUp || !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { - // If we're going to sleep, but it's not from the power button, use the default reveal. - // If we're waking up, only use the default reveal if the biometric controller didn't - // already set it to the circular reveal because we're waking up from a fingerprint/face - // auth. - mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE); - mLightRevealScrim.setRevealAmount(1f - mStatusBarStateController.getDozeAmount()); - } - } - - public LightRevealScrim getLightRevealScrim() { - return mLightRevealScrim; - } - - public void onTrackingStarted() { - mShadeController.runPostCollapseRunnables(); - } - - public void onClosingFinished() { - mShadeController.runPostCollapseRunnables(); - if (!mPresenter.isPresenterFullyCollapsed()) { - // if we set it not to be focusable when collapsing, we have to undo it when we aborted - // the closing - mNotificationShadeWindowController.setNotificationShadeFocusable(true); - } - } + void onHintFinished(); - public void onUnlockHintStarted() { - mFalsingCollector.onUnlockHintStarted(); - mKeyguardIndicationController.showActionToUnlock(); - } + void onCameraHintStarted(); - public void onHintFinished() { - // Delay the reset a bit so the user can read the text. - mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS); - } + void onVoiceAssistHintStarted(); - public void onCameraHintStarted() { - mFalsingCollector.onCameraHintStarted(); - mKeyguardIndicationController.showTransientIndication(R.string.camera_hint); - } + void onPhoneHintStarted(); - public void onVoiceAssistHintStarted() { - mFalsingCollector.onLeftAffordanceHintStarted(); - mKeyguardIndicationController.showTransientIndication(R.string.voice_hint); - } - - public void onPhoneHintStarted() { - mFalsingCollector.onLeftAffordanceHintStarted(); - mKeyguardIndicationController.showTransientIndication(R.string.phone_hint); - } - - public void onTrackingStopped(boolean expand) { - if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { - if (!expand && !mKeyguardStateController.canDismissLockScreen()) { - mStatusBarKeyguardViewManager.showBouncer(false /* scrimmed */); - } - } - } + void onTrackingStopped(boolean expand); // TODO: Figure out way to remove these. - public NavigationBarView getNavigationBarView() { - return mNavigationBarController.getNavigationBarView(mDisplayId); - } + NavigationBarView getNavigationBarView(); - public boolean isOverviewEnabled() { - return mNavigationBarController.isOverviewEnabled(mDisplayId); - } + boolean isOverviewEnabled(); - public void showPinningEnterExitToast(boolean entering) { - mNavigationBarController.showPinningEnterExitToast(mDisplayId, entering); - } + void showPinningEnterExitToast(boolean entering); - public void showPinningEscapeToast() { - mNavigationBarController.showPinningEscapeToast(mDisplayId); - } + void showPinningEscapeToast(); - /** - * TODO: Remove this method. Views should not be passed forward. Will cause theme issues. - * @return bottom area view - */ - public KeyguardBottomAreaView getKeyguardBottomAreaView() { - return mNotificationPanelViewController.getKeyguardBottomAreaView(); - } + KeyguardBottomAreaView getKeyguardBottomAreaView(); - /** - * Propagation of the bouncer state, indicating that it's fully visible. - */ - public void setBouncerShowing(boolean bouncerShowing) { - mBouncerShowing = bouncerShowing; - mKeyguardBypassController.setBouncerShowing(bouncerShowing); - mPulseExpansionHandler.setBouncerShowing(bouncerShowing); - setBouncerShowingForStatusBarComponents(bouncerShowing); - mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing); - mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */); - updateScrimController(); - if (!mBouncerShowing) { - updatePanelExpansionForKeyguard(); - } - } + void setBouncerShowing(boolean bouncerShowing); - /** - * Propagate the bouncer state to status bar components. - * - * Separate from {@link #setBouncerShowing} because we sometimes re-create the status bar and - * should update only the status bar components. - */ - private void setBouncerShowingForStatusBarComponents(boolean bouncerShowing) { - int importance = bouncerShowing - ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - : IMPORTANT_FOR_ACCESSIBILITY_AUTO; - if (mPhoneStatusBarViewController != null) { - mPhoneStatusBarViewController.setImportantForAccessibility(importance); - } - mNotificationPanelViewController.setImportantForAccessibility(importance); - mNotificationPanelViewController.setBouncerShowing(bouncerShowing); - } + void collapseShade(); - /** - * Collapses the notification shade if it is tracking or expanded. - */ - public void collapseShade() { - if (mNotificationPanelViewController.isTracking()) { - mNotificationShadeWindowViewController.cancelCurrentTouch(); - } - if (mPanelExpanded && mState == StatusBarState.SHADE) { - mShadeController.animateCollapsePanels(); - } - } + int getWakefulnessState(); - @VisibleForTesting - final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() { - @Override - public void onFinishedGoingToSleep() { - mNotificationPanelViewController.onAffordanceLaunchEnded(); - releaseGestureWakeLock(); - mLaunchCameraWhenFinishedWaking = false; - mDeviceInteractive = false; - mWakeUpComingFromTouch = false; - mWakeUpTouchLocation = null; - updateVisibleToUser(); - - updateNotificationPanelTouchState(); - mNotificationShadeWindowViewController.cancelCurrentTouch(); - if (mLaunchCameraOnFinishedGoingToSleep) { - mLaunchCameraOnFinishedGoingToSleep = false; - - // This gets executed before we will show Keyguard, so post it in order that the state - // is correct. - mMainExecutor.execute(() -> mCommandQueueCallbacks.onCameraLaunchGestureDetected( - mLastCameraLaunchSource)); - } + boolean isScreenFullyOff(); - if (mLaunchEmergencyActionOnFinishedGoingToSleep) { - mLaunchEmergencyActionOnFinishedGoingToSleep = false; + void showScreenPinningRequest(int taskId, boolean allowCancel); - // This gets executed before we will show Keyguard, so post it in order that the - // state is correct. - mMainExecutor.execute( - () -> mCommandQueueCallbacks.onEmergencyActionLaunchGestureDetected()); - } - updateIsKeyguard(); - } - - @Override - public void onStartedGoingToSleep() { - String tag = "CentralSurfaces#onStartedGoingToSleep"; - DejankUtils.startDetectingBlockingIpcs(tag); - - // cancel stale runnables that could put the device in the wrong state - cancelAfterLaunchTransitionRunnables(); - - updateRevealEffect(false /* wakingUp */); - updateNotificationPanelTouchState(); - maybeEscalateHeadsUp(); - dismissVolumeDialog(); - mWakeUpCoordinator.setFullyAwake(false); - mKeyguardBypassController.onStartedGoingToSleep(); - - // The unlocked screen off and fold to aod animations might use our LightRevealScrim - - // we need to be expanded for it to be visible. - if (mDozeParameters.shouldShowLightRevealScrim()) { - makeExpandedVisible(true); - } - - DejankUtils.stopDetectingBlockingIpcs(tag); - } - - @Override - public void onStartedWakingUp() { - String tag = "CentralSurfaces#onStartedWakingUp"; - DejankUtils.startDetectingBlockingIpcs(tag); - mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> { - mDeviceInteractive = true; - mWakeUpCoordinator.setWakingUp(true); - if (!mKeyguardBypassController.getBypassEnabled()) { - mHeadsUpManager.releaseAllImmediately(); - } - updateVisibleToUser(); - updateIsKeyguard(); - mDozeServiceHost.stopDozing(); - // This is intentionally below the stopDozing call above, since it avoids that we're - // unnecessarily animating the wakeUp transition. Animations should only be enabled - // once we fully woke up. - updateRevealEffect(true /* wakingUp */); - updateNotificationPanelTouchState(); - - // If we are waking up during the screen off animation, we should undo making the - // expanded visible (we did that so the LightRevealScrim would be visible). - if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) { - makeExpandedInvisible(); - } - - }); - DejankUtils.stopDetectingBlockingIpcs(tag); - } - - @Override - public void onFinishedWakingUp() { - mWakeUpCoordinator.setFullyAwake(true); - mWakeUpCoordinator.setWakingUp(false); - if (mLaunchCameraWhenFinishedWaking) { - mNotificationPanelViewController.launchCamera( - false /* animate */, mLastCameraLaunchSource); - mLaunchCameraWhenFinishedWaking = false; - } - if (mLaunchEmergencyActionWhenFinishedWaking) { - mLaunchEmergencyActionWhenFinishedWaking = false; - Intent emergencyIntent = getEmergencyActionIntent(); - if (emergencyIntent != null) { - mContext.startActivityAsUser(emergencyIntent, - getActivityUserHandle(emergencyIntent)); - } - } - updateScrimController(); - } - }; - - /** - * We need to disable touch events because these might - * collapse the panel after we expanded it, and thus we would end up with a blank - * Keyguard. - */ - void updateNotificationPanelTouchState() { - boolean goingToSleepWithoutAnimation = isGoingToSleep() - && !mDozeParameters.shouldControlScreenOff(); - boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing()) - || goingToSleepWithoutAnimation; - mNotificationPanelViewController.setTouchAndAnimationDisabled(disabled); - mNotificationIconAreaController.setAnimationsEnabled(!disabled); - } - - final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { - @Override - public void onScreenTurningOn(Runnable onDrawn) { - mFalsingCollector.onScreenTurningOn(); - mNotificationPanelViewController.onScreenTurningOn(); - } - - @Override - public void onScreenTurnedOn() { - mScrimController.onScreenTurnedOn(); - } - - @Override - public void onScreenTurnedOff() { - Trace.beginSection("CentralSurfaces#onScreenTurnedOff"); - mFalsingCollector.onScreenOff(); - mScrimController.onScreenTurnedOff(); - if (mCloseQsBeforeScreenOff) { - mNotificationPanelViewController.closeQs(); - mCloseQsBeforeScreenOff = false; - } - updateIsKeyguard(); - Trace.endSection(); - } - }; - - public int getWakefulnessState() { - return mWakefulnessLifecycle.getWakefulness(); - } - - /** - * @return true if the screen is currently fully off, i.e. has finished turning off and has - * since not started turning on. - */ - public boolean isScreenFullyOff() { - return mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_OFF; - } - - public void showScreenPinningRequest(int taskId, boolean allowCancel) { - mScreenPinningRequest.showPrompt(taskId, allowCancel); - } - - @Nullable Intent getEmergencyActionIntent() { - Intent emergencyIntent = new Intent(EmergencyGesture.ACTION_LAUNCH_EMERGENCY); - PackageManager pm = mContext.getPackageManager(); - List<ResolveInfo> emergencyActivities = pm.queryIntentActivities(emergencyIntent, - PackageManager.MATCH_SYSTEM_ONLY); - ResolveInfo resolveInfo = getTopEmergencySosInfo(emergencyActivities); - if (resolveInfo == null) { - Log.wtf(TAG, "Couldn't find an app to process the emergency intent."); - return null; - } - emergencyIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName, - resolveInfo.activityInfo.name)); - emergencyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - return emergencyIntent; - } - - /** - * Select and return the "best" ResolveInfo for Emergency SOS Activity. - */ - private @Nullable ResolveInfo getTopEmergencySosInfo(List<ResolveInfo> emergencyActivities) { - // No matched activity. - if (emergencyActivities == null || emergencyActivities.isEmpty()) { - return null; - } - - // Of multiple matched Activities, give preference to the pre-set package name. - String preferredAppPackageName = - mContext.getString(R.string.config_preferredEmergencySosPackage); - - // If there is no preferred app, then return first match. - if (TextUtils.isEmpty(preferredAppPackageName)) { - return emergencyActivities.get(0); - } - - for (ResolveInfo emergencyInfo: emergencyActivities) { - // If activity is from the preferred app, use it. - if (TextUtils.equals(emergencyInfo.activityInfo.packageName, preferredAppPackageName)) { - return emergencyInfo; - } - } - // No matching activity: return first match - return emergencyActivities.get(0); - } - - boolean isCameraAllowedByAdmin() { - if (mDevicePolicyManager.getCameraDisabled(null, - mLockscreenUserManager.getCurrentUserId())) { - return false; - } else if (mStatusBarKeyguardViewManager == null - || (isKeyguardShowing() && isKeyguardSecure())) { - // Check if the admin has disabled the camera specifically for the keyguard - return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, - mLockscreenUserManager.getCurrentUserId()) - & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0; - } - return true; - } + @Nullable + Intent getEmergencyActionIntent(); - boolean isGoingToSleep() { - return mWakefulnessLifecycle.getWakefulness() - == WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP; - } + boolean isCameraAllowedByAdmin(); - boolean isWakingOrAwake() { - return mWakefulnessLifecycle.getWakefulness() == WakefulnessLifecycle.WAKEFULNESS_WAKING - || mWakefulnessLifecycle.getWakefulness() == WakefulnessLifecycle.WAKEFULNESS_AWAKE; - } + boolean isGoingToSleep(); - public void notifyBiometricAuthModeChanged() { - mDozeServiceHost.updateDozing(); - updateScrimController(); - } + void notifyBiometricAuthModeChanged(); - /** - * Set the amount of progress we are currently in if we're transitioning to the full shade. - * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full - * shade. - */ - public void setTransitionToFullShadeProgress(float transitionToFullShadeProgress) { - mTransitionToFullShadeProgress = transitionToFullShadeProgress; - } + void setTransitionToFullShadeProgress(float transitionToFullShadeProgress); - /** - * Sets the amount of progress to the bouncer being fully hidden/visible. 1 means the bouncer - * is fully hidden, while 0 means the bouncer is visible. - */ - public void setBouncerHiddenFraction(float expansion) { - mScrimController.setBouncerHiddenFraction(expansion); - } + void setBouncerHiddenFraction(float expansion); @VisibleForTesting - public void updateScrimController() { - Trace.beginSection("CentralSurfaces#updateScrimController"); - - boolean unlocking = mKeyguardStateController.isShowing() && ( - mBiometricUnlockController.isWakeAndUnlock() - || mKeyguardStateController.isKeyguardFadingAway() - || mKeyguardStateController.isKeyguardGoingAway() - || mKeyguardViewMediator.requestedShowSurfaceBehindKeyguard() - || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()); - - mScrimController.setExpansionAffectsAlpha(!unlocking); - - boolean launchingAffordanceWithPreview = - mNotificationPanelViewController.isLaunchingAffordanceWithPreview(); - mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview); - - if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { - if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED - || mTransitionToFullShadeProgress > 0f) { - mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE); - } else { - mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED); - } - } else if (mBouncerShowing && !unlocking) { - // Bouncer needs the front scrim when it's on top of an activity, - // tapping on a notification, editing QS or being dismissed by - // FLAG_DISMISS_KEYGUARD_ACTIVITY. - ScrimState state = mStatusBarKeyguardViewManager.bouncerNeedsScrimming() - ? ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER; - mScrimController.transitionTo(state); - } else if (launchingAffordanceWithPreview) { - // We want to avoid animating when launching with a preview. - mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); - } else if (mBrightnessMirrorVisible) { - mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR); - } else if (mState == StatusBarState.SHADE_LOCKED) { - mScrimController.transitionTo(ScrimState.SHADE_LOCKED); - } else if (mDozeServiceHost.isPulsing()) { - mScrimController.transitionTo(ScrimState.PULSING, - mDozeScrimController.getScrimCallback()); - } else if (mDozeServiceHost.hasPendingScreenOffCallback()) { - mScrimController.transitionTo(ScrimState.OFF, new ScrimController.Callback() { - @Override - public void onFinished() { - mDozeServiceHost.executePendingScreenOffCallback(); - } - }); - } else if (mDozing && !unlocking) { - mScrimController.transitionTo(ScrimState.AOD); - } else if (mKeyguardStateController.isShowing() && !isOccluded() && !unlocking) { - mScrimController.transitionTo(ScrimState.KEYGUARD); - } else if (mKeyguardStateController.isShowing() && mKeyguardUpdateMonitor.isDreaming()) { - mScrimController.transitionTo(ScrimState.DREAMING); - } else { - mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); - } - updateLightRevealScrimVisibility(); - - Trace.endSection(); - } - - public boolean isKeyguardShowing() { - if (mStatusBarKeyguardViewManager == null) { - Slog.i(TAG, "isKeyguardShowing() called before startKeyguard(), returning true"); - return true; - } - return mStatusBarKeyguardViewManager.isShowing(); - } - - public boolean shouldIgnoreTouch() { - return (mStatusBarStateController.isDozing() - && mDozeServiceHost.getIgnoreTouchWhilePulsing()) - || mScreenOffAnimationController.shouldIgnoreKeyguardTouches(); - } - - // Begin Extra BaseStatusBar methods. - - protected final CommandQueue mCommandQueue; - protected IStatusBarService mBarService; - - // all notifications - protected NotificationStackScrollLayout mStackScroller; + void updateScrimController(); - // handling reordering - private final VisualStabilityManager mVisualStabilityManager; + boolean isKeyguardShowing(); - protected AccessibilityManager mAccessibilityManager; + boolean shouldIgnoreTouch(); - protected boolean mDeviceInteractive; + boolean isDeviceInteractive(); - protected boolean mVisible; + void setNotificationSnoozed(StatusBarNotification sbn, + NotificationSwipeActionHelper.SnoozeOption snoozeOption); - // mScreenOnFromKeyguard && mVisible. - private boolean mVisibleToUser; + void awakenDreams(); - protected DevicePolicyManager mDevicePolicyManager; - private final PowerManager mPowerManager; - protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - - protected KeyguardManager mKeyguardManager; - private final DeviceProvisionedController mDeviceProvisionedController; - - private final NavigationBarController mNavigationBarController; - private final AccessibilityFloatingMenuController mAccessibilityFloatingMenuController; - - // UI-specific methods - - protected WindowManager mWindowManager; - protected IWindowManager mWindowManagerService; - private IDreamManager mDreamManager; - - protected Display mDisplay; - private int mDisplayId; - - protected NotificationShelfController mNotificationShelfController; - - private final Lazy<AssistManager> mAssistManagerLazy; - - public boolean isDeviceInteractive() { - return mDeviceInteractive; - } - - private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) { - NotificationManager noMan = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - noMan.cancel(com.android.internal.messages.nano.SystemMessageProto.SystemMessage. - NOTE_HIDDEN_NOTIFICATIONS); - - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); - if (BANNER_ACTION_SETUP.equals(action)) { - mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */); - mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - - ); - } - } - } - }; - - public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) { - mNotificationsController.setNotificationSnoozed(sbn, snoozeOption); - } + @Override + void startPendingIntentDismissingKeyguard(PendingIntent intent); + @Override + void startPendingIntentDismissingKeyguard( + PendingIntent intent, @Nullable Runnable intentSentUiThreadCallback); - public void awakenDreams() { - mUiBgExecutor.execute(() -> { - try { - mDreamManager.awaken(); - } catch (RemoteException e) { - e.printStackTrace(); - } - }); - } + @Override + void startPendingIntentDismissingKeyguard(PendingIntent intent, + Runnable intentSentUiThreadCallback, View associatedView); - protected void toggleKeyboardShortcuts(int deviceId) { - KeyboardShortcuts.toggle(mContext, deviceId); - } + @Override + void startPendingIntentDismissingKeyguard( + PendingIntent intent, @Nullable Runnable intentSentUiThreadCallback, + @Nullable ActivityLaunchAnimator.Controller animationController); - protected void dismissKeyboardShortcuts() { - KeyboardShortcuts.dismiss(); - } + void clearNotificationEffects(); - /** - * Dismiss the keyguard then execute an action. - * - * @param action The action to execute after dismissing the keyguard. - * @param collapsePanel Whether we should collapse the panel after dismissing the keyguard. - * @param willAnimateOnKeyguard Whether {@param action} will run an animation on the keyguard if - * we are locked. - */ - private void executeActionDismissingKeyguard(Runnable action, boolean afterKeyguardGone, - boolean collapsePanel, boolean willAnimateOnKeyguard) { - if (!mDeviceProvisionedController.isDeviceProvisioned()) return; - - OnDismissAction onDismissAction = new OnDismissAction() { - @Override - public boolean onDismiss() { - new Thread(() -> { - try { - // The intent we are sending is for the application, which - // won't have permission to immediately start an activity after - // the user switches to home. We know it is safe to do at this - // point, so make sure new activity switches are now allowed. - ActivityManager.getService().resumeAppSwitches(); - } catch (RemoteException e) { - } - action.run(); - }).start(); - - return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard; - } + boolean isBouncerShowing(); - @Override - public boolean willRunAnimationOnKeyguard() { - return willAnimateOnKeyguard; - } - }; - dismissKeyguardThenExecute(onDismissAction, afterKeyguardGone); - } + boolean isBouncerShowingScrimmed(); - @Override - public void startPendingIntentDismissingKeyguard(final PendingIntent intent) { - startPendingIntentDismissingKeyguard(intent, null); - } + boolean isBouncerShowingOverDream(); - @Override - public void startPendingIntentDismissingKeyguard( - final PendingIntent intent, @Nullable final Runnable intentSentUiThreadCallback) { - startPendingIntentDismissingKeyguard(intent, intentSentUiThreadCallback, - (ActivityLaunchAnimator.Controller) null); - } + void onBouncerPreHideAnimation(); - @Override - public void startPendingIntentDismissingKeyguard(PendingIntent intent, - Runnable intentSentUiThreadCallback, View associatedView) { - ActivityLaunchAnimator.Controller animationController = null; - if (associatedView instanceof ExpandableNotificationRow) { - animationController = mNotificationAnimationProvider.getAnimatorController( - ((ExpandableNotificationRow) associatedView)); - } + boolean isKeyguardSecure(); - startPendingIntentDismissingKeyguard(intent, intentSentUiThreadCallback, - animationController); - } + NotificationPanelViewController getPanelController(); - @Override - public void startPendingIntentDismissingKeyguard( - final PendingIntent intent, @Nullable final Runnable intentSentUiThreadCallback, - @Nullable ActivityLaunchAnimator.Controller animationController) { - final boolean willLaunchResolverActivity = intent.isActivity() - && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(), - mLockscreenUserManager.getCurrentUserId()); - - boolean animate = !willLaunchResolverActivity - && animationController != null - && shouldAnimateLaunch(intent.isActivity()); - - // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we run - // the animation on the keyguard). The animation will take care of (instantly) collapsing - // the shade and hiding the keyguard once it is done. - boolean collapse = !animate; - executeActionDismissingKeyguard(() -> { - try { - // We wrap animationCallback with a StatusBarLaunchAnimatorController so that the - // shade is collapsed after the animation (or when it is cancelled, aborted, etc). - ActivityLaunchAnimator.Controller controller = - animationController != null ? new StatusBarLaunchAnimatorController( - animationController, this, intent.isActivity()) : null; - - mActivityLaunchAnimator.startPendingIntentWithAnimation( - controller, animate, intent.getCreatorPackage(), - (animationAdapter) -> { - ActivityOptions options = new ActivityOptions( - getActivityOptions(mDisplayId, animationAdapter)); - // TODO b/221255671: restrict this to only be set for notifications - options.setEligibleForLegacyPermissionPrompt(true); - return intent.sendAndReturnResult(null, 0, null, null, null, - null, options.toBundle()); - }); - } catch (PendingIntent.CanceledException e) { - // the stack trace isn't very helpful here. - // Just log the exception message. - Log.w(TAG, "Sending intent failed: " + e); - if (!collapse) { - // executeActionDismissingKeyguard did not collapse for us already. - collapsePanelOnMainThread(); - } - // TODO: Dismiss Keyguard. - } - if (intent.isActivity()) { - mAssistManagerLazy.get().hideAssist(); - } - if (intentSentUiThreadCallback != null) { - postOnUiThread(intentSentUiThreadCallback); - } - }, willLaunchResolverActivity, collapse, animate); - } + NotificationGutsManager getGutsManager(); - private void postOnUiThread(Runnable runnable) { - mMainExecutor.execute(runnable); - } + void updateNotificationPanelTouchState(); - /** - * Returns an ActivityOptions bundle created using the given parameters. - * - * @param displayId The ID of the display to launch the activity in. Typically this would be the - * display the status bar is on. - * @param animationAdapter The animation adapter used to start this activity, or {@code null} - * for the default animation. - */ - public static Bundle getActivityOptions(int displayId, - @Nullable RemoteAnimationAdapter animationAdapter) { - ActivityOptions options = getDefaultActivityOptions(animationAdapter); - options.setLaunchDisplayId(displayId); - options.setCallerDisplayId(displayId); - return options.toBundle(); - } + void makeExpandedVisible(boolean force); - /** - * Returns an ActivityOptions bundle created using the given parameters. - * - * @param displayId The ID of the display to launch the activity in. Typically this would be the - * display the status bar is on. - * @param animationAdapter The animation adapter used to start this activity, or {@code null} - * for the default animation. - * @param isKeyguardShowing Whether keyguard is currently showing. - * @param eventTime The event time in milliseconds since boot, not including sleep. See - * {@link ActivityOptions#setSourceInfo}. - */ - public static Bundle getActivityOptions(int displayId, - @Nullable RemoteAnimationAdapter animationAdapter, boolean isKeyguardShowing, - long eventTime) { - ActivityOptions options = getDefaultActivityOptions(animationAdapter); - options.setSourceInfo(isKeyguardShowing ? ActivityOptions.SourceInfo.TYPE_LOCKSCREEN - : ActivityOptions.SourceInfo.TYPE_NOTIFICATION, eventTime); - options.setLaunchDisplayId(displayId); - options.setCallerDisplayId(displayId); - return options.toBundle(); - } + void instantCollapseNotificationPanel(); - public static ActivityOptions getDefaultActivityOptions( - @Nullable RemoteAnimationAdapter animationAdapter) { - ActivityOptions options; - if (animationAdapter != null) { - if (ENABLE_SHELL_TRANSITIONS) { - options = ActivityOptions.makeRemoteTransition( - RemoteTransitionAdapter.adaptRemoteAnimation(animationAdapter)); - } else { - options = ActivityOptions.makeRemoteAnimation(animationAdapter); - } - } else { - options = ActivityOptions.makeBasic(); - } - options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR); - return options; - } + void visibilityChanged(boolean visible); - void visibilityChanged(boolean visible) { - if (mVisible != visible) { - mVisible = visible; - if (!visible) { - mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, - true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */); - } - } - updateVisibleToUser(); - } + int getDisplayId(); - protected void updateVisibleToUser() { - boolean oldVisibleToUser = mVisibleToUser; - mVisibleToUser = mVisible && mDeviceInteractive; + int getRotation(); - if (oldVisibleToUser != mVisibleToUser) { - handleVisibleToUserChanged(mVisibleToUser); - } - } + @VisibleForTesting + void setBarStateForTest(int state); - /** - * Clear Buzz/Beep/Blink. - */ - public void clearNotificationEffects() { - try { - mBarService.clearNotificationEffects(); - } catch (RemoteException e) { - // Won't fail unless the world has ended. - } - } + void wakeUpForFullScreenIntent(); - /** - * @return Whether the security bouncer from Keyguard is showing. - */ - public boolean isBouncerShowing() { - return mBouncerShowing; - } + void showTransientUnchecked(); - /** - * @return Whether the security bouncer from Keyguard is showing. - */ - public boolean isBouncerShowingScrimmed() { - return isBouncerShowing() && mStatusBarKeyguardViewManager.bouncerNeedsScrimming(); - } + void clearTransient(); - public boolean isBouncerShowingOverDream() { - return isBouncerShowing() && mDreamOverlayStateController.isOverlayActive(); - } + void acquireGestureWakeLock(long time); - /** - * When {@link KeyguardBouncer} starts to be dismissed, playing its animation. - */ - public void onBouncerPreHideAnimation() { - mNotificationPanelViewController.onBouncerPreHideAnimation(); + boolean setAppearance(int appearance); - } + int getBarMode(); - /** - * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then - * return PackageManager for mContext - */ - public static PackageManager getPackageManagerForUser(Context context, int userId) { - Context contextForUser = context; - // UserHandle defines special userId as negative values, e.g. USER_ALL - if (userId >= 0) { - try { - // Create a context for the correct user so if a package isn't installed - // for user 0 we can still load information about the package. - contextForUser = - context.createPackageContextAsUser(context.getPackageName(), - Context.CONTEXT_RESTRICTED, - new UserHandle(userId)); - } catch (NameNotFoundException e) { - // Shouldn't fail to find the package name for system ui. - } - } - return contextForUser.getPackageManager(); - } + void resendMessage(int msg); - public boolean isKeyguardSecure() { - if (mStatusBarKeyguardViewManager == null) { - // startKeyguard() hasn't been called yet, so we don't know. - // Make sure anything that needs to know isKeyguardSecure() checks and re-checks this - // value onVisibilityChanged(). - Slog.w(TAG, "isKeyguardSecure() called before startKeyguard(), returning false", - new Throwable()); - return false; - } - return mStatusBarKeyguardViewManager.isSecure(); - } - public NotificationPanelViewController getPanelController() { - return mNotificationPanelViewController; - } - // End Extra BaseStatusBarMethods. + void resendMessage(Object msg); - public NotificationGutsManager getGutsManager() { - return mGutsManager; - } + int getDisabled1(); - boolean isTransientShown() { - return mTransientShown; - } + void setDisabled1(int disabled); - private void updateLightRevealScrimVisibility() { - if (mLightRevealScrim == null) { - // status bar may not be inflated yet - return; - } + int getDisabled2(); - mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha()); - } + void setDisabled2(int disabled); - private final KeyguardUpdateMonitorCallback mUpdateCallback = - new KeyguardUpdateMonitorCallback() { - @Override - public void onDreamingStateChanged(boolean dreaming) { - updateScrimController(); - if (dreaming) { - maybeEscalateHeadsUp(); - } - } - - // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by - // KeyguardCoordinator - @Override - public void onStrongAuthStateChanged(int userId) { - super.onStrongAuthStateChanged(userId); - mNotificationsController.requestNotificationUpdate("onStrongAuthStateChanged"); - } - }; - - - private final FalsingManager.FalsingBeliefListener mFalsingBeliefListener = - new FalsingManager.FalsingBeliefListener() { - @Override - public void onFalse() { - // Hides quick settings, bouncer, and quick-quick settings. - mStatusBarKeyguardViewManager.reset(true); - } - }; - - // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over, - // this animation is tied to the scrim for historic reasons. - // TODO: notify when keyguard has faded away instead of the scrim. - private final ScrimController.Callback mUnlockScrimCallback = new ScrimController - .Callback() { - @Override - public void onFinished() { - if (mStatusBarKeyguardViewManager == null) { - Log.w(TAG, "Tried to notify keyguard visibility when " - + "mStatusBarKeyguardViewManager was null"); - return; - } - if (mKeyguardStateController.isKeyguardFadingAway()) { - mStatusBarKeyguardViewManager.onKeyguardFadedAway(); - } - } + void setLastCameraLaunchSource(int source); - @Override - public void onCancelled() { - onFinished(); - } - }; - - private final DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() { - @Override - public void onUserSetupChanged() { - final boolean userSetup = mDeviceProvisionedController.isCurrentUserSetup(); - Log.d(TAG, "mUserSetupObserver - DeviceProvisionedListener called for " - + "current user"); - if (MULTIUSER_DEBUG) { - Log.d(TAG, String.format("User setup changed: userSetup=%s mUserSetup=%s", - userSetup, mUserSetup)); - } + void setLaunchCameraOnFinishedGoingToSleep(boolean launch); - if (userSetup != mUserSetup) { - mUserSetup = userSetup; - if (!mUserSetup) { - animateCollapseQuickSettings(); - } - if (mNotificationPanelViewController != null) { - mNotificationPanelViewController.setUserSetupComplete(mUserSetup); - } - updateQsExpansionEnabled(); - } - } - }; - - private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (!mWallpaperSupported) { - // Receiver should not have been registered at all... - Log.wtf(TAG, "WallpaperManager not supported"); - return; - } - WallpaperInfo info = mWallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT); - mWallpaperController.onWallpaperInfoUpdated(info); - - final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_dozeSupportsAodWallpaper); - // If WallpaperInfo is null, it must be ImageWallpaper. - final boolean supportsAmbientMode = deviceSupportsAodWallpaper - && (info != null && info.supportsAmbientMode()); - - mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode); - mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode); - mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode); - } - }; + void setLaunchCameraOnFinishedWaking(boolean launch); - private final ConfigurationListener mConfigurationListener = new ConfigurationListener() { - @Override - public void onConfigChanged(Configuration newConfig) { - updateResources(); - updateDisplaySize(); // populates mDisplayMetrics + void setLaunchEmergencyActionOnFinishedGoingToSleep(boolean launch); - if (DEBUG) { - Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration()); - } + void setLaunchEmergencyActionOnFinishedWaking(boolean launch); - if (!mNotifPipelineFlags.isNewPipelineEnabled()) { - mViewHierarchyManager.updateRowStates(); - } - mScreenPinningRequest.onConfigurationChanged(); - } + void setTopHidesStatusBar(boolean hides); - @Override - public void onDensityOrFontScaleChanged() { - // TODO: Remove this. - if (mBrightnessMirrorController != null) { - mBrightnessMirrorController.onDensityOrFontScaleChanged(); - } - // TODO: Bring these out of CentralSurfaces. - mUserInfoControllerImpl.onDensityOrFontScaleChanged(); - mUserSwitcherController.onDensityOrFontScaleChanged(); - mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext); - mHeadsUpManager.onDensityOrFontScaleChanged(); - } + QSPanelController getQSPanelController(); - @Override - public void onThemeChanged() { - if (mBrightnessMirrorController != null) { - mBrightnessMirrorController.onOverlayChanged(); - } - // We need the new R.id.keyguard_indication_area before recreating - // mKeyguardIndicationController - mNotificationPanelViewController.onThemeChanged(); + boolean areNotificationAlertsDisabled(); - if (mStatusBarKeyguardViewManager != null) { - mStatusBarKeyguardViewManager.onThemeChanged(); - } - if (mAmbientIndicationContainer instanceof AutoReinflateContainer) { - ((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout(); - } - mNotificationIconAreaController.onThemeChanged(); - } + float getDisplayDensity(); - @Override - public void onUiModeChanged() { - if (mBrightnessMirrorController != null) { - mBrightnessMirrorController.onUiModeChanged(); - } - } - }; - - private StatusBarStateController.StateListener mStateListener = - new StatusBarStateController.StateListener() { - @Override - public void onStatePreChange(int oldState, int newState) { - // If we're visible and switched to SHADE_LOCKED (the user dragged - // down on the lockscreen), clear notification LED, vibration, - // ringing. - // Other transitions are covered in handleVisibleToUserChanged(). - if (mVisible && (newState == StatusBarState.SHADE_LOCKED - || mStatusBarStateController.goingToFullShade())) { - clearNotificationEffects(); - } - if (newState == StatusBarState.KEYGUARD) { - mRemoteInputManager.onPanelCollapsed(); - maybeEscalateHeadsUp(); - } - } - - @Override - public void onStateChanged(int newState) { - mState = newState; - updateReportRejectedTouchVisibility(); - mDozeServiceHost.updateDozing(); - updateTheme(); - mNavigationBarController.touchAutoDim(mDisplayId); - Trace.beginSection("CentralSurfaces#updateKeyguardState"); - if (mState == StatusBarState.KEYGUARD) { - mNotificationPanelViewController.cancelPendingPanelCollapse(); - } - updateDozingState(); - checkBarModes(); - updateScrimController(); - mPresenter.updateMediaMetaData(false, mState != StatusBarState.KEYGUARD); - Trace.endSection(); - } - - @Override - public void onDozeAmountChanged(float linear, float eased) { - if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS) - && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { - mLightRevealScrim.setRevealAmount(1f - linear); - } - } - - @Override - public void onDozingChanged(boolean isDozing) { - Trace.beginSection("CentralSurfaces#updateDozing"); - mDozing = isDozing; - - // Collapse the notification panel if open - boolean dozingAnimated = mDozeServiceHost.getDozingRequested() - && mDozeParameters.shouldControlScreenOff(); - mNotificationPanelViewController.resetViews(dozingAnimated); - - updateQsExpansionEnabled(); - mKeyguardViewMediator.setDozing(mDozing); - - mNotificationsController.requestNotificationUpdate("onDozingChanged"); - updateDozingState(); - mDozeServiceHost.updateDozing(); - updateScrimController(); - updateReportRejectedTouchVisibility(); - Trace.endSection(); - } - - @Override - public void onFullscreenStateChanged(boolean isFullscreen) { - mIsFullscreen = isFullscreen; - maybeUpdateBarMode(); - } - }; - - private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback = - new BatteryController.BatteryStateChangeCallback() { - @Override - public void onPowerSaveChanged(boolean isPowerSave) { - mMainExecutor.execute(mCheckBarModes); - if (mDozeServiceHost != null) { - mDozeServiceHost.firePowerSaveChanged(isPowerSave); - } - } - }; - - private final ActivityLaunchAnimator.Callback mActivityLaunchAnimatorCallback = - new ActivityLaunchAnimator.Callback() { - @Override - public boolean isOnKeyguard() { - return mKeyguardStateController.isShowing(); - } - - @Override - public void hideKeyguardWithAnimation(IRemoteAnimationRunner runner) { - // We post to the main thread for 2 reasons: - // 1. KeyguardViewMediator is not thread-safe. - // 2. To ensure that ViewMediatorCallback#keyguardDonePending is called before - // ViewMediatorCallback#readyForKeyguardDone. The wrong order could occur - // when doing - // dismissKeyguardThenExecute { hideKeyguardWithAnimation(runner) }. - mMainExecutor.execute(() -> mKeyguardViewMediator.hideWithAnimation(runner)); - } - - @Override - public int getBackgroundColor(TaskInfo task) { - if (!mStartingSurfaceOptional.isPresent()) { - Log.w(TAG, "No starting surface, defaulting to SystemBGColor"); - return SplashscreenContentDrawer.getSystemBGColor(); - } - - return mStartingSurfaceOptional.get().getBackgroundColor(task); - } - }; - - private final ActivityLaunchAnimator.Listener mActivityLaunchAnimatorListener = - new ActivityLaunchAnimator.Listener() { - @Override - public void onLaunchAnimationStart() { - mKeyguardViewMediator.setBlursDisabledForAppLaunch(true); - } - - @Override - public void onLaunchAnimationEnd() { - mKeyguardViewMediator.setBlursDisabledForAppLaunch(false); - } - }; - - private final DemoMode mDemoModeCallback = new DemoMode() { - @Override - public void onDemoModeFinished() { - checkBarModes(); - } + void extendDozePulse(); - @Override - public void dispatchDemoCommand(String command, Bundle args) { } - }; + public static class KeyboardShortcutsMessage { + final int mDeviceId; - /** - * Determines what UserHandle to use when launching an activity. - * - * We want to ensure that activities that are launched within the systemui process should be - * launched as user of the current process. - * @param intent - * @return UserHandle - */ - private UserHandle getActivityUserHandle(Intent intent) { - String[] packages = mContext.getResources().getStringArray(R.array.system_ui_packages); - for (String pkg : packages) { - if (intent.getComponent() == null) break; - if (pkg.equals(intent.getComponent().getPackageName())) { - return new UserHandle(UserHandle.myUserId()); - } + KeyboardShortcutsMessage(int deviceId) { + mDeviceId = deviceId; } - return UserHandle.CURRENT; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java new file mode 100644 index 000000000000..e4efb98528fe --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -0,0 +1,4551 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; +import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; +import static android.app.StatusBarManager.WindowVisibleState; +import static android.app.StatusBarManager.windowStateToString; +import static android.view.InsetsState.ITYPE_STATUS_BAR; +import static android.view.InsetsState.containsType; +import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; +import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS; +import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS; + +import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; +import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; +import static androidx.lifecycle.Lifecycle.State.RESUMED; + +import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; +import static com.android.systemui.charging.WirelessChargingLayout.UNKNOWN_BATTERY_LEVEL; +import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; +import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; + +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.ActivityTaskManager; +import android.app.IWallpaperManager; +import android.app.KeyguardManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.StatusBarManager; +import android.app.TaskInfo; +import android.app.TaskStackBuilder; +import android.app.UiModeManager; +import android.app.WallpaperInfo; +import android.app.WallpaperManager; +import android.app.admin.DevicePolicyManager; +import android.content.BroadcastReceiver; +import android.content.ComponentCallbacks2; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Configuration; +import android.graphics.Point; +import android.graphics.PointF; +import android.hardware.devicestate.DeviceStateManager; +import android.metrics.LogMaker; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.Trace; +import android.os.UserHandle; +import android.provider.Settings; +import android.service.dreams.DreamService; +import android.service.dreams.IDreamManager; +import android.service.notification.StatusBarNotification; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.DisplayMetrics; +import android.util.EventLog; +import android.util.IndentingPrintWriter; +import android.util.Log; +import android.util.MathUtils; +import android.util.Slog; +import android.view.Display; +import android.view.IRemoteAnimationRunner; +import android.view.IWindowManager; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ThreadedRenderer; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowInsetsController.Appearance; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.accessibility.AccessibilityManager; +import android.widget.DateTimeView; +import android.window.SplashScreen; + +import androidx.annotation.NonNull; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleRegistry; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.colorextraction.ColorExtractor; +import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.statusbar.RegisterStatusBarResult; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.keyguard.ViewMediatorCallback; +import com.android.systemui.ActivityIntentHelper; +import com.android.systemui.AutoReinflateContainer; +import com.android.systemui.CoreStartable; +import com.android.systemui.DejankUtils; +import com.android.systemui.EventLogTags; +import com.android.systemui.InitController; +import com.android.systemui.Prefs; +import com.android.systemui.R; +import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; +import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.DelegateLaunchAnimatorController; +import com.android.systemui.assist.AssistManager; +import com.android.systemui.biometrics.AuthRippleController; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.camera.CameraIntents; +import com.android.systemui.charging.WirelessChargingAnimation; +import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dagger.qualifiers.UiBackground; +import com.android.systemui.demomode.DemoMode; +import com.android.systemui.demomode.DemoModeController; +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.emergency.EmergencyGesture; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; +import com.android.systemui.fragments.ExtensionFragmentListener; +import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.fragments.FragmentService; +import com.android.systemui.keyguard.KeyguardService; +import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.keyguard.ScreenLifecycle; +import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.navigationbar.NavigationBarController; +import com.android.systemui.navigationbar.NavigationBarView; +import com.android.systemui.plugins.DarkIconDispatcher; +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.OverlayPlugin; +import com.android.systemui.plugins.PluginDependencyProvider; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.plugins.qs.QS; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.qs.QSFragment; +import com.android.systemui.qs.QSPanelController; +import com.android.systemui.recents.ScreenPinningRequest; +import com.android.systemui.scrim.ScrimView; +import com.android.systemui.settings.brightness.BrightnessSliderController; +import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.statusbar.AutoHideUiElement; +import com.android.systemui.statusbar.BackDropView; +import com.android.systemui.statusbar.CircleReveal; +import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.GestureRecorder; +import com.android.systemui.statusbar.KeyboardShortcuts; +import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.statusbar.LiftReveal; +import com.android.systemui.statusbar.LightRevealScrim; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.NotificationShelfController; +import com.android.systemui.statusbar.NotificationViewHierarchyManager; +import com.android.systemui.statusbar.PowerButtonReveal; +import com.android.systemui.statusbar.PulseExpansionHandler; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.charging.WiredChargingRippleController; +import com.android.systemui.statusbar.connectivity.NetworkController; +import com.android.systemui.statusbar.core.StatusBarInitializer; +import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.NotifPipelineFlags; +import com.android.systemui.statusbar.notification.NotificationActivityStarter; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; +import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; +import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; +import com.android.systemui.statusbar.notification.init.NotificationsController; +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; +import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule; +import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; +import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.BrightnessMirrorController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; +import com.android.systemui.statusbar.policy.ExtensionController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.policy.UserInfoControllerImpl; +import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowStateController; +import com.android.systemui.util.DumpUtilsKt; +import com.android.systemui.util.WallpaperController; +import com.android.systemui.util.concurrency.DelayableExecutor; +import com.android.systemui.util.concurrency.MessageRouter; +import com.android.systemui.volume.VolumeComponent; +import com.android.systemui.wmshell.BubblesManager; +import com.android.wm.shell.bubbles.Bubbles; +import com.android.wm.shell.startingsurface.SplashscreenContentDrawer; +import com.android.wm.shell.startingsurface.StartingSurface; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Executor; + +import javax.inject.Inject; +import javax.inject.Named; + +import dagger.Lazy; + +/** + * A class handling initialization and coordination between some of the key central surfaces in + * System UI: The notification shade, the keyguard (lockscreen), and the status bar. + * + * This class is not our ideal architecture because it doesn't enforce much isolation between these + * three mostly disparate surfaces. In an ideal world, this class would not exist. Instead, we would + * break it up into three modules -- one for each of those three surfaces -- and we would define any + * APIs that are needed for these surfaces to communicate with each other when necessary. + * + * <b>If at all possible, please avoid adding additional code to this monstrous class! Our goal is + * to break up this class into many small classes, and any code added here will slow down that goal. + * </b> + */ +@SysUISingleton +public class CentralSurfacesImpl extends CoreStartable implements + CentralSurfaces { + + private static final String BANNER_ACTION_CANCEL = + "com.android.systemui.statusbar.banner_action_cancel"; + private static final String BANNER_ACTION_SETUP = + "com.android.systemui.statusbar.banner_action_setup"; + + private static final int MSG_OPEN_SETTINGS_PANEL = 1002; + private static final int MSG_LAUNCH_TRANSITION_TIMEOUT = 1003; + // 1020-1040 reserved for BaseStatusBar + + /** + * The delay to reset the hint text when the hint animation is finished running. + */ + private static final int HINT_RESET_DELAY_MS = 1200; + + private static final UiEventLogger sUiEventLogger = new UiEventLoggerImpl(); + + /** + * If true, the system is in the half-boot-to-decryption-screen state. + * Prudently disable QS and notifications. + */ + public static final boolean ONLY_CORE_APPS; + + static { + boolean onlyCoreApps; + try { + IPackageManager packageManager = + IPackageManager.Stub.asInterface(ServiceManager.getService("package")); + onlyCoreApps = packageManager != null && packageManager.isOnlyCoreApps(); + } catch (RemoteException e) { + onlyCoreApps = false; + } + ONLY_CORE_APPS = onlyCoreApps; + } + + private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; + private final DreamOverlayStateController mDreamOverlayStateController; + private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks; + private float mTransitionToFullShadeProgress = 0f; + private NotificationListContainer mNotifListContainer; + + private final KeyguardStateController.Callback mKeyguardStateControllerCallback = + new KeyguardStateController.Callback() { + @Override + public void onKeyguardShowingChanged() { + boolean occluded = mKeyguardStateController.isOccluded(); + mStatusBarHideIconsForBouncerManager.setIsOccludedAndTriggerUpdate(occluded); + mScrimController.setKeyguardOccluded(occluded); + } + }; + + void onStatusBarWindowStateChanged(@WindowVisibleState int state) { + updateBubblesVisibility(); + mStatusBarWindowState = state; + } + + @Override + public void acquireGestureWakeLock(long time) { + mGestureWakeLock.acquire(time); + } + + @Override + public boolean setAppearance(int appearance) { + if (mAppearance != appearance) { + mAppearance = appearance; + return updateBarMode(barMode(isTransientShown(), appearance)); + } + + return false; + } + + @Override + public int getBarMode() { + return mStatusBarMode; + } + + @Override + public void resendMessage(int msg) { + mMessageRouter.cancelMessages(msg); + mMessageRouter.sendMessage(msg); + } + + @Override + public void resendMessage(Object msg) { + mMessageRouter.cancelMessages(msg.getClass()); + mMessageRouter.sendMessage(msg); + } + + @Override + public int getDisabled1() { + return mDisabled1; + } + + @Override + public void setDisabled1(int disabled) { + mDisabled1 = disabled; + } + + @Override + public int getDisabled2() { + return mDisabled2; + } + + @Override + public void setDisabled2(int disabled) { + mDisabled2 = disabled; + } + + @Override + public void setLastCameraLaunchSource(int source) { + mLastCameraLaunchSource = source; + } + + @Override + public void setLaunchCameraOnFinishedGoingToSleep(boolean launch) { + mLaunchCameraOnFinishedGoingToSleep = launch; + } + + @Override + public void setLaunchCameraOnFinishedWaking(boolean launch) { + mLaunchCameraWhenFinishedWaking = launch; + } + + @Override + public void setLaunchEmergencyActionOnFinishedGoingToSleep(boolean launch) { + mLaunchEmergencyActionOnFinishedGoingToSleep = launch; + } + + @Override + public void setLaunchEmergencyActionOnFinishedWaking(boolean launch) { + mLaunchEmergencyActionWhenFinishedWaking = launch; + } + + @Override + public void setTopHidesStatusBar(boolean hides) { + mTopHidesStatusBar = hides; + } + + @Override + public QSPanelController getQSPanelController() { + return mQSPanelController; + } + + /** */ + @Override + public void animateExpandNotificationsPanel() { + mCommandQueueCallbacks.animateExpandNotificationsPanel(); + } + + /** */ + @Override + public void animateExpandSettingsPanel(@Nullable String subpanel) { + mCommandQueueCallbacks.animateExpandSettingsPanel(subpanel); + } + + /** */ + @Override + public void animateCollapsePanels(int flags, boolean force) { + mCommandQueueCallbacks.animateCollapsePanels(flags, force); + } + + /** */ + @Override + public void togglePanel() { + mCommandQueueCallbacks.togglePanel(); + } + /** + * The {@link StatusBarState} of the status bar. + */ + protected int mState; // TODO: remove this. Just use StatusBarStateController + protected boolean mBouncerShowing; + + private final PhoneStatusBarPolicy mIconPolicy; + + private final VolumeComponent mVolumeComponent; + private BrightnessMirrorController mBrightnessMirrorController; + private boolean mBrightnessMirrorVisible; + private BiometricUnlockController mBiometricUnlockController; + private final LightBarController mLightBarController; + private final Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy; + private final LockscreenGestureLogger mLockscreenGestureLogger; + @Nullable + protected LockscreenWallpaper mLockscreenWallpaper; + private final AutoHideController mAutoHideController; + + private final Point mCurrentDisplaySize = new Point(); + + protected NotificationShadeWindowView mNotificationShadeWindowView; + protected PhoneStatusBarView mStatusBarView; + private PhoneStatusBarViewController mPhoneStatusBarViewController; + private PhoneStatusBarTransitions mStatusBarTransitions; + private AuthRippleController mAuthRippleController; + @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING; + protected final NotificationShadeWindowController mNotificationShadeWindowController; + private final StatusBarWindowController mStatusBarWindowController; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @VisibleForTesting + DozeServiceHost mDozeServiceHost; + private boolean mWakeUpComingFromTouch; + private PointF mWakeUpTouchLocation; + private LightRevealScrim mLightRevealScrim; + private PowerButtonReveal mPowerButtonReveal; + + private final Object mQueueLock = new Object(); + + private final PulseExpansionHandler mPulseExpansionHandler; + private final NotificationWakeUpCoordinator mWakeUpCoordinator; + private final KeyguardBypassController mKeyguardBypassController; + private final KeyguardStateController mKeyguardStateController; + private final HeadsUpManagerPhone mHeadsUpManager; + private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; + private final DynamicPrivacyController mDynamicPrivacyController; + private final FalsingCollector mFalsingCollector; + private final FalsingManager mFalsingManager; + private final BroadcastDispatcher mBroadcastDispatcher; + private final ConfigurationController mConfigurationController; + protected NotificationShadeWindowViewController mNotificationShadeWindowViewController; + private final DozeParameters mDozeParameters; + private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; + private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory; + private final PluginManager mPluginManager; + private final ShadeController mShadeController; + private final InitController mInitController; + + private final PluginDependencyProvider mPluginDependencyProvider; + private final KeyguardDismissUtil mKeyguardDismissUtil; + private final ExtensionController mExtensionController; + private final UserInfoControllerImpl mUserInfoControllerImpl; + private final DemoModeController mDemoModeController; + private final NotificationsController mNotificationsController; + private final OngoingCallController mOngoingCallController; + private final StatusBarSignalPolicy mStatusBarSignalPolicy; + private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; + + // expanded notifications + // the sliding/resizing panel within the notification window + protected NotificationPanelViewController mNotificationPanelViewController; + + // settings + private QSPanelController mQSPanelController; + + KeyguardIndicationController mKeyguardIndicationController; + + private View mReportRejectedTouch; + + private boolean mExpandedVisible; + + private final int[] mAbsPos = new int[2]; + + private final NotifShadeEventSource mNotifShadeEventSource; + protected final NotificationEntryManager mEntryManager; + private final NotificationGutsManager mGutsManager; + private final NotificationLogger mNotificationLogger; + private final NotificationViewHierarchyManager mViewHierarchyManager; + private final PanelExpansionStateManager mPanelExpansionStateManager; + private final KeyguardViewMediator mKeyguardViewMediator; + protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider; + private final BrightnessSliderController.Factory mBrightnessSliderFactory; + private final FeatureFlags mFeatureFlags; + private final FragmentService mFragmentService; + private final ScreenOffAnimationController mScreenOffAnimationController; + private final WallpaperController mWallpaperController; + private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; + private final MessageRouter mMessageRouter; + private final WallpaperManager mWallpaperManager; + + private CentralSurfacesComponent mCentralSurfacesComponent; + + // Flags for disabling the status bar + // Two variables becaseu the first one evidently ran out of room for new flags. + private int mDisabled1 = 0; + private int mDisabled2 = 0; + + /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */ + private @Appearance int mAppearance; + + private boolean mTransientShown; + + private final DisplayMetrics mDisplayMetrics; + + // XXX: gesture research + private final GestureRecorder mGestureRec = DEBUG_GESTURES + ? new GestureRecorder("/sdcard/statusbar_gestures.dat") + : null; + + private final ScreenPinningRequest mScreenPinningRequest; + + private final MetricsLogger mMetricsLogger; + + // ensure quick settings is disabled until the current user makes it through the setup wizard + @VisibleForTesting + protected boolean mUserSetup = false; + + @VisibleForTesting + public enum StatusBarUiEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "Secured lockscreen is opened.") + LOCKSCREEN_OPEN_SECURE(405), + + @UiEvent(doc = "Lockscreen without security is opened.") + LOCKSCREEN_OPEN_INSECURE(406), + + @UiEvent(doc = "Secured lockscreen is closed.") + LOCKSCREEN_CLOSE_SECURE(407), + + @UiEvent(doc = "Lockscreen without security is closed.") + LOCKSCREEN_CLOSE_INSECURE(408), + + @UiEvent(doc = "Secured bouncer is opened.") + BOUNCER_OPEN_SECURE(409), + + @UiEvent(doc = "Bouncer without security is opened.") + BOUNCER_OPEN_INSECURE(410), + + @UiEvent(doc = "Secured bouncer is closed.") + BOUNCER_CLOSE_SECURE(411), + + @UiEvent(doc = "Bouncer without security is closed.") + BOUNCER_CLOSE_INSECURE(412); + + private final int mId; + + StatusBarUiEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } + + private Handler mMainHandler; + private final DelayableExecutor mMainExecutor; + + private int mInteractingWindows; + private @TransitionMode int mStatusBarMode; + + private final ViewMediatorCallback mKeyguardViewMediatorCallback; + private final ScrimController mScrimController; + protected DozeScrimController mDozeScrimController; + private final Executor mUiBgExecutor; + + protected boolean mDozing; + private boolean mIsFullscreen; + + boolean mCloseQsBeforeScreenOff; + + private final NotificationMediaManager mMediaManager; + private final NotificationLockscreenUserManager mLockscreenUserManager; + private final NotificationRemoteInputManager mRemoteInputManager; + private boolean mWallpaperSupported; + + private Runnable mLaunchTransitionEndRunnable; + private Runnable mLaunchTransitionCancelRunnable; + private boolean mLaunchCameraWhenFinishedWaking; + private boolean mLaunchCameraOnFinishedGoingToSleep; + private boolean mLaunchEmergencyActionWhenFinishedWaking; + private boolean mLaunchEmergencyActionOnFinishedGoingToSleep; + private int mLastCameraLaunchSource; + protected PowerManager.WakeLock mGestureWakeLock; + + private final int[] mTmpInt2 = new int[2]; + + // Fingerprint (as computed by getLoggingFingerprint() of the last logged state. + private int mLastLoggedStateFingerprint; + private boolean mTopHidesStatusBar; + private boolean mStatusBarWindowHidden; + private boolean mIsLaunchingActivityOverLockscreen; + + private final UserSwitcherController mUserSwitcherController; + private final NetworkController mNetworkController; + private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); + protected final BatteryController mBatteryController; + protected boolean mPanelExpanded; + private UiModeManager mUiModeManager; + private LogMaker mStatusBarStateLog; + protected final NotificationIconAreaController mNotificationIconAreaController; + @Nullable private View mAmbientIndicationContainer; + private final SysuiColorExtractor mColorExtractor; + private final ScreenLifecycle mScreenLifecycle; + private final WakefulnessLifecycle mWakefulnessLifecycle; + + private boolean mNoAnimationOnNextBarModeChange; + private final SysuiStatusBarStateController mStatusBarStateController; + + private final ActivityLaunchAnimator mActivityLaunchAnimator; + private NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider; + protected NotificationPresenter mPresenter; + private NotificationActivityStarter mNotificationActivityStarter; + private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy; + private final Optional<BubblesManager> mBubblesManagerOptional; + private final Optional<Bubbles> mBubblesOptional; + private final Bubbles.BubbleExpandListener mBubbleExpandListener; + private final Optional<StartingSurface> mStartingSurfaceOptional; + private final NotifPipelineFlags mNotifPipelineFlags; + + private final ActivityIntentHelper mActivityIntentHelper; + private NotificationStackScrollLayoutController mStackScrollerController; + + private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener = + (extractor, which) -> updateTheme(); + + private final InteractionJankMonitor mJankMonitor; + + + /** + * Public constructor for CentralSurfaces. + * + * CentralSurfaces is considered optional, and therefore can not be marked as @Inject directly. + * Instead, an @Provide method is included. See {@link StatusBarPhoneModule}. + */ + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + @Inject + public CentralSurfacesImpl( + Context context, + NotificationsController notificationsController, + FragmentService fragmentService, + LightBarController lightBarController, + AutoHideController autoHideController, + StatusBarWindowController statusBarWindowController, + StatusBarWindowStateController statusBarWindowStateController, + KeyguardUpdateMonitor keyguardUpdateMonitor, + StatusBarSignalPolicy statusBarSignalPolicy, + PulseExpansionHandler pulseExpansionHandler, + NotificationWakeUpCoordinator notificationWakeUpCoordinator, + KeyguardBypassController keyguardBypassController, + KeyguardStateController keyguardStateController, + HeadsUpManagerPhone headsUpManagerPhone, + DynamicPrivacyController dynamicPrivacyController, + FalsingManager falsingManager, + FalsingCollector falsingCollector, + BroadcastDispatcher broadcastDispatcher, + NotifShadeEventSource notifShadeEventSource, + NotificationEntryManager notificationEntryManager, + NotificationGutsManager notificationGutsManager, + NotificationLogger notificationLogger, + NotificationInterruptStateProvider notificationInterruptStateProvider, + NotificationViewHierarchyManager notificationViewHierarchyManager, + PanelExpansionStateManager panelExpansionStateManager, + KeyguardViewMediator keyguardViewMediator, + DisplayMetrics displayMetrics, + MetricsLogger metricsLogger, + @UiBackground Executor uiBgExecutor, + NotificationMediaManager notificationMediaManager, + NotificationLockscreenUserManager lockScreenUserManager, + NotificationRemoteInputManager remoteInputManager, + UserSwitcherController userSwitcherController, + NetworkController networkController, + BatteryController batteryController, + SysuiColorExtractor colorExtractor, + ScreenLifecycle screenLifecycle, + WakefulnessLifecycle wakefulnessLifecycle, + SysuiStatusBarStateController statusBarStateController, + Optional<BubblesManager> bubblesManagerOptional, + Optional<Bubbles> bubblesOptional, + VisualStabilityManager visualStabilityManager, + DeviceProvisionedController deviceProvisionedController, + NavigationBarController navigationBarController, + AccessibilityFloatingMenuController accessibilityFloatingMenuController, + Lazy<AssistManager> assistManagerLazy, + ConfigurationController configurationController, + NotificationShadeWindowController notificationShadeWindowController, + DozeParameters dozeParameters, + ScrimController scrimController, + Lazy<LockscreenWallpaper> lockscreenWallpaperLazy, + LockscreenGestureLogger lockscreenGestureLogger, + Lazy<BiometricUnlockController> biometricUnlockControllerLazy, + DozeServiceHost dozeServiceHost, + PowerManager powerManager, + ScreenPinningRequest screenPinningRequest, + DozeScrimController dozeScrimController, + VolumeComponent volumeComponent, + CommandQueue commandQueue, + CentralSurfacesComponent.Factory centralSurfacesComponentFactory, + PluginManager pluginManager, + ShadeController shadeController, + StatusBarKeyguardViewManager statusBarKeyguardViewManager, + ViewMediatorCallback viewMediatorCallback, + InitController initController, + @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler, + PluginDependencyProvider pluginDependencyProvider, + KeyguardDismissUtil keyguardDismissUtil, + ExtensionController extensionController, + UserInfoControllerImpl userInfoControllerImpl, + PhoneStatusBarPolicy phoneStatusBarPolicy, + KeyguardIndicationController keyguardIndicationController, + DemoModeController demoModeController, + Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy, + StatusBarTouchableRegionManager statusBarTouchableRegionManager, + NotificationIconAreaController notificationIconAreaController, + BrightnessSliderController.Factory brightnessSliderFactory, + ScreenOffAnimationController screenOffAnimationController, + WallpaperController wallpaperController, + OngoingCallController ongoingCallController, + StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, + LockscreenShadeTransitionController lockscreenShadeTransitionController, + FeatureFlags featureFlags, + KeyguardUnlockAnimationController keyguardUnlockAnimationController, + @Main Handler mainHandler, + @Main DelayableExecutor delayableExecutor, + @Main MessageRouter messageRouter, + WallpaperManager wallpaperManager, + Optional<StartingSurface> startingSurfaceOptional, + ActivityLaunchAnimator activityLaunchAnimator, + NotifPipelineFlags notifPipelineFlags, + InteractionJankMonitor jankMonitor, + DeviceStateManager deviceStateManager, + DreamOverlayStateController dreamOverlayStateController, + WiredChargingRippleController wiredChargingRippleController) { + super(context); + mNotificationsController = notificationsController; + mFragmentService = fragmentService; + mLightBarController = lightBarController; + mAutoHideController = autoHideController; + mStatusBarWindowController = statusBarWindowController; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mPulseExpansionHandler = pulseExpansionHandler; + mWakeUpCoordinator = notificationWakeUpCoordinator; + mKeyguardBypassController = keyguardBypassController; + mKeyguardStateController = keyguardStateController; + mHeadsUpManager = headsUpManagerPhone; + mKeyguardIndicationController = keyguardIndicationController; + mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; + mDynamicPrivacyController = dynamicPrivacyController; + mFalsingCollector = falsingCollector; + mFalsingManager = falsingManager; + mBroadcastDispatcher = broadcastDispatcher; + mNotifShadeEventSource = notifShadeEventSource; + mEntryManager = notificationEntryManager; + mGutsManager = notificationGutsManager; + mNotificationLogger = notificationLogger; + mNotificationInterruptStateProvider = notificationInterruptStateProvider; + mViewHierarchyManager = notificationViewHierarchyManager; + mPanelExpansionStateManager = panelExpansionStateManager; + mKeyguardViewMediator = keyguardViewMediator; + mDisplayMetrics = displayMetrics; + mMetricsLogger = metricsLogger; + mUiBgExecutor = uiBgExecutor; + mMediaManager = notificationMediaManager; + mLockscreenUserManager = lockScreenUserManager; + mRemoteInputManager = remoteInputManager; + mUserSwitcherController = userSwitcherController; + mNetworkController = networkController; + mBatteryController = batteryController; + mColorExtractor = colorExtractor; + mScreenLifecycle = screenLifecycle; + mWakefulnessLifecycle = wakefulnessLifecycle; + mStatusBarStateController = statusBarStateController; + mBubblesManagerOptional = bubblesManagerOptional; + mBubblesOptional = bubblesOptional; + mVisualStabilityManager = visualStabilityManager; + mDeviceProvisionedController = deviceProvisionedController; + mNavigationBarController = navigationBarController; + mAccessibilityFloatingMenuController = accessibilityFloatingMenuController; + mAssistManagerLazy = assistManagerLazy; + mConfigurationController = configurationController; + mNotificationShadeWindowController = notificationShadeWindowController; + mDozeServiceHost = dozeServiceHost; + mPowerManager = powerManager; + mDozeParameters = dozeParameters; + mScrimController = scrimController; + mLockscreenWallpaperLazy = lockscreenWallpaperLazy; + mLockscreenGestureLogger = lockscreenGestureLogger; + mScreenPinningRequest = screenPinningRequest; + mDozeScrimController = dozeScrimController; + mBiometricUnlockControllerLazy = biometricUnlockControllerLazy; + mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy; + mVolumeComponent = volumeComponent; + mCommandQueue = commandQueue; + mCentralSurfacesComponentFactory = centralSurfacesComponentFactory; + mPluginManager = pluginManager; + mShadeController = shadeController; + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mKeyguardViewMediatorCallback = viewMediatorCallback; + mInitController = initController; + mPluginDependencyProvider = pluginDependencyProvider; + mKeyguardDismissUtil = keyguardDismissUtil; + mExtensionController = extensionController; + mUserInfoControllerImpl = userInfoControllerImpl; + mIconPolicy = phoneStatusBarPolicy; + mDemoModeController = demoModeController; + mNotificationIconAreaController = notificationIconAreaController; + mBrightnessSliderFactory = brightnessSliderFactory; + mWallpaperController = wallpaperController; + mOngoingCallController = ongoingCallController; + mStatusBarSignalPolicy = statusBarSignalPolicy; + mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; + mFeatureFlags = featureFlags; + mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; + mMainHandler = mainHandler; + mMainExecutor = delayableExecutor; + mMessageRouter = messageRouter; + mWallpaperManager = wallpaperManager; + mJankMonitor = jankMonitor; + mDreamOverlayStateController = dreamOverlayStateController; + + mLockscreenShadeTransitionController = lockscreenShadeTransitionController; + mStartingSurfaceOptional = startingSurfaceOptional; + mNotifPipelineFlags = notifPipelineFlags; + lockscreenShadeTransitionController.setCentralSurfaces(this); + statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged); + + mScreenOffAnimationController = screenOffAnimationController; + + mPanelExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged); + + mBubbleExpandListener = + (isExpanding, key) -> mContext.getMainExecutor().execute(() -> { + mNotificationsController.requestNotificationUpdate("onBubbleExpandChanged"); + updateScrimController(); + }); + + mActivityIntentHelper = new ActivityIntentHelper(mContext); + mActivityLaunchAnimator = activityLaunchAnimator; + + // The status bar background may need updating when the ongoing call status changes. + mOngoingCallController.addCallback((animate) -> maybeUpdateBarMode()); + + // TODO(b/190746471): Find a better home for this. + DateTimeView.setReceiverHandler(timeTickHandler); + + mMessageRouter.subscribeTo(KeyboardShortcutsMessage.class, + data -> toggleKeyboardShortcuts(data.mDeviceId)); + mMessageRouter.subscribeTo(MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU, + id -> dismissKeyboardShortcuts()); + mMessageRouter.subscribeTo(AnimateExpandSettingsPanelMessage.class, + data -> mCommandQueueCallbacks.animateExpandSettingsPanel(data.mSubpanel)); + mMessageRouter.subscribeTo(MSG_LAUNCH_TRANSITION_TIMEOUT, + id -> onLaunchTransitionTimeout()); + + deviceStateManager.registerCallback(mMainExecutor, + new FoldStateListener(mContext, this::onFoldedStateChanged)); + wiredChargingRippleController.registerCallbacks(); + } + + @Override + public void start() { + mScreenLifecycle.addObserver(mScreenObserver); + mWakefulnessLifecycle.addObserver(mWakefulnessObserver); + mUiModeManager = mContext.getSystemService(UiModeManager.class); + if (mBubblesOptional.isPresent()) { + mBubblesOptional.get().setExpandListener(mBubbleExpandListener); + } + + mStatusBarSignalPolicy.init(); + mKeyguardIndicationController.init(); + + mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener); + mStatusBarStateController.addCallback(mStateListener, + SysuiStatusBarStateController.RANK_STATUS_BAR); + + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + mDreamManager = IDreamManager.Stub.asInterface( + ServiceManager.checkService(DreamService.DREAM_SERVICE)); + + mDisplay = mContext.getDisplay(); + mDisplayId = mDisplay.getDisplayId(); + updateDisplaySize(); + mStatusBarHideIconsForBouncerManager.setDisplayId(mDisplayId); + + // start old BaseStatusBar.start(). + mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); + mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService( + Context.DEVICE_POLICY_SERVICE); + + mAccessibilityManager = (AccessibilityManager) + mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); + + mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController); + mBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + + mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); + mWallpaperSupported = mWallpaperManager.isWallpaperSupported(); + + RegisterStatusBarResult result = null; + try { + result = mBarService.registerStatusBar(mCommandQueue); + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + } + + createAndAddWindows(result); + + if (mWallpaperSupported) { + // Make sure we always have the most current wallpaper info. + IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); + mBroadcastDispatcher.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter, + null /* handler */, UserHandle.ALL); + mWallpaperChangedReceiver.onReceive(mContext, null); + } else if (DEBUG) { + Log.v(TAG, "start(): no wallpaper service "); + } + + // Set up the initial notification state. This needs to happen before CommandQueue.disable() + setUpPresenter(); + + if (containsType(result.mTransientBarTypes, ITYPE_STATUS_BAR)) { + showTransientUnchecked(); + } + mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance, + result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior, + result.mRequestedVisibilities, result.mPackageName); + + // StatusBarManagerService has a back up of IME token and it's restored here. + mCommandQueueCallbacks.setImeWindowStatus(mDisplayId, result.mImeToken, + result.mImeWindowVis, result.mImeBackDisposition, result.mShowImeSwitcher); + + // Set up the initial icon state + int numIcons = result.mIcons.size(); + for (int i = 0; i < numIcons; i++) { + mCommandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i)); + } + + if (DEBUG) { + Log.d(TAG, String.format( + "init: icons=%d disabled=0x%08x lights=0x%08x imeButton=0x%08x", + numIcons, + result.mDisabledFlags1, + result.mAppearance, + result.mImeWindowVis)); + } + + IntentFilter internalFilter = new IntentFilter(); + internalFilter.addAction(BANNER_ACTION_CANCEL); + internalFilter.addAction(BANNER_ACTION_SETUP); + mContext.registerReceiver(mBannerActionBroadcastReceiver, internalFilter, PERMISSION_SELF, + null, Context.RECEIVER_EXPORTED_UNAUDITED); + + if (mWallpaperSupported) { + IWallpaperManager wallpaperManager = IWallpaperManager.Stub.asInterface( + ServiceManager.getService(Context.WALLPAPER_SERVICE)); + try { + wallpaperManager.setInAmbientMode(false /* ambientMode */, 0L /* duration */); + } catch (RemoteException e) { + // Just pass, nothing critical. + } + } + + // end old BaseStatusBar.start(). + + // Lastly, call to the icon policy to install/update all the icons. + mIconPolicy.init(); + + mKeyguardStateController.addCallback(new KeyguardStateController.Callback() { + @Override + public void onUnlockedChanged() { + logStateToEventlog(); + } + }); + startKeyguard(); + + mKeyguardUpdateMonitor.registerCallback(mUpdateCallback); + mDozeServiceHost.initialize( + this, + mStatusBarKeyguardViewManager, + mNotificationShadeWindowViewController, + mNotificationPanelViewController, + mAmbientIndicationContainer); + updateLightRevealScrimVisibility(); + + mConfigurationController.addCallback(mConfigurationListener); + + mBatteryController.observe(mLifecycle, mBatteryStateChangeCallback); + mLifecycle.setCurrentState(RESUMED); + + mAccessibilityFloatingMenuController.init(); + + // set the initial view visibility + int disabledFlags1 = result.mDisabledFlags1; + int disabledFlags2 = result.mDisabledFlags2; + mInitController.addPostInitTask( + () -> setUpDisableFlags(disabledFlags1, disabledFlags2)); + + mFalsingManager.addFalsingBeliefListener(mFalsingBeliefListener); + + mPluginManager.addPluginListener( + new PluginListener<OverlayPlugin>() { + private final ArraySet<OverlayPlugin> mOverlays = new ArraySet<>(); + + @Override + public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) { + mMainExecutor.execute( + () -> plugin.setup(getNotificationShadeWindowView(), + getNavigationBarView(), + new Callback(plugin), mDozeParameters)); + } + + @Override + public void onPluginDisconnected(OverlayPlugin plugin) { + mMainExecutor.execute(() -> { + mOverlays.remove(plugin); + mNotificationShadeWindowController + .setForcePluginOpen(mOverlays.size() != 0, this); + }); + } + + class Callback implements OverlayPlugin.Callback { + private final OverlayPlugin mPlugin; + + Callback(OverlayPlugin plugin) { + mPlugin = plugin; + } + + @Override + public void onHoldStatusBarOpenChange() { + if (mPlugin.holdStatusBarOpen()) { + mOverlays.add(mPlugin); + } else { + mOverlays.remove(mPlugin); + } + mMainExecutor.execute(() -> { + mNotificationShadeWindowController + .setStateListener(b -> mOverlays.forEach( + o -> o.setCollapseDesired(b))); + mNotificationShadeWindowController + .setForcePluginOpen(mOverlays.size() != 0, this); + }); + } + } + }, OverlayPlugin.class, true /* Allow multiple plugins */); + + mStartingSurfaceOptional.ifPresent(startingSurface -> startingSurface.setSysuiProxy( + (requestTopUi, componentTag) -> mMainExecutor.execute(() -> + mNotificationShadeWindowController.setRequestTopUi( + requestTopUi, componentTag)))); + } + + private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) { + Trace.beginSection("CentralSurfaces#onFoldedStateChanged"); + onFoldedStateChangedInternal(isFolded, willGoToSleep); + Trace.endSection(); + } + + private void onFoldedStateChangedInternal(boolean isFolded, boolean willGoToSleep) { + // Folded state changes are followed by a screen off event. + // By default turning off the screen also closes the shade. + // We want to make sure that the shade status is kept after + // folding/unfolding. + boolean isShadeOpen = mShadeController.isShadeOpen(); + boolean leaveOpen = isShadeOpen && !willGoToSleep; + if (DEBUG) { + Log.d(TAG, String.format( + "#onFoldedStateChanged(): " + + "isFolded=%s, " + + "willGoToSleep=%s, " + + "isShadeOpen=%s, " + + "leaveOpen=%s", + isFolded, willGoToSleep, isShadeOpen, leaveOpen)); + } + if (leaveOpen) { + mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); + if (mKeyguardStateController.isShowing()) { + // When device state changes on keyguard we don't want to keep the state of + // the shade and instead we open clean state of keyguard with shade closed. + // Normally some parts of QS state (like expanded/collapsed) are persisted and + // that causes incorrect UI rendering, especially when changing state with QS + // expanded. To prevent that we can close QS which resets QS and some parts of + // the shade to its default state. Read more in b/201537421 + mCloseQsBeforeScreenOff = true; + } + } + } + + // ================================================================================ + // Constructing the view + // ================================================================================ + protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) { + updateDisplaySize(); // populates mDisplayMetrics + updateResources(); + updateTheme(); + + inflateStatusBarWindow(); + mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener()); + mWallpaperController.setRootView(mNotificationShadeWindowView); + + // TODO: Deal with the ugliness that comes from having some of the status bar broken out + // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot. + mNotificationLogger.setUpWithContainer(mNotifListContainer); + mNotificationIconAreaController.setupShelf(mNotificationShelfController); + mPanelExpansionStateManager.addExpansionListener(mWakeUpCoordinator); + mUserSwitcherController.init(mNotificationShadeWindowView); + + // Allow plugins to reference DarkIconDispatcher and StatusBarStateController + mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class); + mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class); + + // Set up CollapsedStatusBarFragment and PhoneStatusBarView + StatusBarInitializer initializer = mCentralSurfacesComponent.getStatusBarInitializer(); + initializer.setStatusBarViewUpdatedListener( + (statusBarView, statusBarViewController, statusBarTransitions) -> { + mStatusBarView = statusBarView; + mPhoneStatusBarViewController = statusBarViewController; + mStatusBarTransitions = statusBarTransitions; + mNotificationShadeWindowViewController + .setStatusBarViewController(mPhoneStatusBarViewController); + // Ensure we re-propagate panel expansion values to the panel controller and + // any listeners it may have, such as PanelBar. This will also ensure we + // re-display the notification panel if necessary (for example, if + // a heads-up notification was being displayed and should continue being + // displayed). + mNotificationPanelViewController.updatePanelExpansionAndVisibility(); + setBouncerShowingForStatusBarComponents(mBouncerShowing); + checkBarModes(); + }); + initializer.initializeStatusBar(mCentralSurfacesComponent); + + mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView); + mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener()); + if (!mNotifPipelineFlags.isNewPipelineEnabled()) { + mHeadsUpManager.addListener(mVisualStabilityManager); + } + mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); + + createNavigationBar(result); + + if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) { + mLockscreenWallpaper = mLockscreenWallpaperLazy.get(); + } + + mNotificationPanelViewController.setKeyguardIndicationController( + mKeyguardIndicationController); + + mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById( + R.id.ambient_indication_container); + + mAutoHideController.setStatusBar(new AutoHideUiElement() { + @Override + public void synchronizeState() { + checkBarModes(); + } + + @Override + public boolean shouldHideOnTouch() { + return !mRemoteInputManager.isRemoteInputActive(); + } + + @Override + public boolean isVisible() { + return isTransientShown(); + } + + @Override + public void hide() { + clearTransient(); + } + }); + + ScrimView scrimBehind = mNotificationShadeWindowView.findViewById(R.id.scrim_behind); + ScrimView notificationsScrim = mNotificationShadeWindowView + .findViewById(R.id.scrim_notifications); + ScrimView scrimInFront = mNotificationShadeWindowView.findViewById(R.id.scrim_in_front); + + mScrimController.setScrimVisibleListener(scrimsVisible -> { + mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible); + }); + mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront); + + mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim); + mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> { + Runnable updateOpaqueness = () -> { + mNotificationShadeWindowController.setLightRevealScrimOpaque( + mLightRevealScrim.isScrimOpaque()); + mScreenOffAnimationController + .onScrimOpaqueChanged(mLightRevealScrim.isScrimOpaque()); + }; + if (opaque) { + // Delay making the view opaque for a frame, because it needs some time to render + // otherwise this can lead to a flicker where the scrim doesn't cover the screen + mLightRevealScrim.post(updateOpaqueness); + } else { + updateOpaqueness.run(); + } + }); + + mScreenOffAnimationController.initialize(this, mLightRevealScrim); + updateLightRevealScrimVisibility(); + + mNotificationPanelViewController.initDependencies( + this, + this::makeExpandedInvisible, + mNotificationShelfController); + + BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop); + mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front), + backdrop.findViewById(R.id.backdrop_back), mScrimController, mLockscreenWallpaper); + float maxWallpaperZoom = mContext.getResources().getFloat( + com.android.internal.R.dimen.config_wallpaperMaxScale); + mNotificationShadeDepthControllerLazy.get().addListener(depth -> { + float scale = MathUtils.lerp(maxWallpaperZoom, 1f, depth); + backdrop.setPivotX(backdrop.getWidth() / 2f); + backdrop.setPivotY(backdrop.getHeight() / 2f); + backdrop.setScaleX(scale); + backdrop.setScaleY(scale); + }); + + mNotificationPanelViewController.setUserSetupComplete(mUserSetup); + + // Set up the quick settings tile panel + final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame); + if (container != null) { + FragmentHostManager fragmentHostManager = FragmentHostManager.get(container); + ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame, + mExtensionController + .newExtension(QS.class) + .withPlugin(QS.class) + .withDefault(this::createDefaultQSFragment) + .build()); + mBrightnessMirrorController = new BrightnessMirrorController( + mNotificationShadeWindowView, + mNotificationPanelViewController, + mNotificationShadeDepthControllerLazy.get(), + mBrightnessSliderFactory, + (visible) -> { + mBrightnessMirrorVisible = visible; + updateScrimController(); + }); + fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> { + QS qs = (QS) f; + if (qs instanceof QSFragment) { + mQSPanelController = ((QSFragment) qs).getQSPanelController(); + ((QSFragment) qs).setBrightnessMirrorController(mBrightnessMirrorController); + } + }); + } + + mReportRejectedTouch = mNotificationShadeWindowView + .findViewById(R.id.report_rejected_touch); + if (mReportRejectedTouch != null) { + updateReportRejectedTouchVisibility(); + mReportRejectedTouch.setOnClickListener(v -> { + Uri session = mFalsingManager.reportRejectedTouch(); + if (session == null) { return; } + + StringWriter message = new StringWriter(); + message.write("Build info: "); + message.write(SystemProperties.get("ro.build.description")); + message.write("\nSerial number: "); + message.write(SystemProperties.get("ro.serialno")); + message.write("\n"); + + startActivityDismissingKeyguard(Intent.createChooser(new Intent(Intent.ACTION_SEND) + .setType("*/*") + .putExtra(Intent.EXTRA_SUBJECT, "Rejected touch report") + .putExtra(Intent.EXTRA_STREAM, session) + .putExtra(Intent.EXTRA_TEXT, message.toString()), + "Share rejected touch report") + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), + true /* onlyProvisioned */, true /* dismissShade */); + }); + } + + if (!mPowerManager.isInteractive()) { + mBroadcastReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); + } + mGestureWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, + "sysui:GestureWakeLock"); + + // receive broadcasts + registerBroadcastReceiver(); + + IntentFilter demoFilter = new IntentFilter(); + if (DEBUG_MEDIA_FAKE_ARTWORK) { + demoFilter.addAction(ACTION_FAKE_ARTWORK); + } + mContext.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter, + android.Manifest.permission.DUMP, null, + Context.RECEIVER_EXPORTED_UNAUDITED); + + // listen for USER_SETUP_COMPLETE setting (per-user) + mDeviceProvisionedController.addCallback(mUserSetupObserver); + mUserSetupObserver.onUserSetupChanged(); + + // disable profiling bars, since they overlap and clutter the output on app windows + ThreadedRenderer.overrideProperty("disableProfileBars", "true"); + + // Private API call to make the shadows look better for Recents + ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f)); + } + + + /** + * When swiping up to dismiss the lock screen, the panel expansion fraction goes from 1f to 0f. + * This results in the clock/notifications/other content disappearing off the top of the screen. + * + * We also use the expansion fraction to animate in the app/launcher surface from the bottom of + * the screen, 'pushing' off the notifications and other content. To do this, we dispatch the + * expansion fraction to the KeyguardViewMediator if we're in the process of dismissing the + * keyguard. + */ + private void dispatchPanelExpansionForKeyguardDismiss(float fraction, boolean trackingTouch) { + // Things that mean we're not swiping to dismiss the keyguard, and should ignore this + // expansion: + // - Keyguard isn't even visible. + // - Keyguard is occluded. Expansion changes here are the shade being expanded over the + // occluding activity. + // - Keyguard is visible, but can't be dismissed (swiping up will show PIN/password prompt). + // - The SIM is locked, you can't swipe to unlock. If the SIM is locked but there is no + // device lock set, canDismissLockScreen returns true even though you should not be able + // to dismiss the lock screen until entering the SIM PIN. + // - QS is expanded and we're swiping - swiping up now will hide QS, not dismiss the + // keyguard. + if (!isKeyguardShowing() + || isOccluded() + || !mKeyguardStateController.canDismissLockScreen() + || mKeyguardViewMediator.isAnySimPinSecure() + || (mNotificationPanelViewController.isQsExpanded() && trackingTouch)) { + return; + } + + // Otherwise, we should let the keyguard know about this if we're tracking touch, or if we + // are already animating the keyguard dismiss (since we will need to either finish or cancel + // the animation). + if (trackingTouch + || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe() + || mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) { + mKeyguardStateController.notifyKeyguardDismissAmountChanged( + 1f - fraction, trackingTouch); + } + } + + private void onPanelExpansionChanged(PanelExpansionChangeEvent event) { + float fraction = event.getFraction(); + boolean tracking = event.getTracking(); + dispatchPanelExpansionForKeyguardDismiss(fraction, tracking); + + if (fraction == 0 || fraction == 1) { + if (getNavigationBarView() != null) { + getNavigationBarView().onStatusBarPanelStateChanged(); + } + if (getNotificationPanelViewController() != null) { + getNotificationPanelViewController().updateSystemUiStateFlags(); + } + } + } + + @NonNull + @Override + public Lifecycle getLifecycle() { + return mLifecycle; + } + + @VisibleForTesting + protected void registerBroadcastReceiver() { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + filter.addAction(Intent.ACTION_SCREEN_OFF); + mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, null, UserHandle.ALL); + } + + protected QS createDefaultQSFragment() { + return FragmentHostManager.get(mNotificationShadeWindowView).create(QSFragment.class); + } + + private void setUpPresenter() { + // Set up the initial notification state. + mActivityLaunchAnimator.setCallback(mActivityLaunchAnimatorCallback); + mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener); + mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider( + mNotificationShadeWindowViewController, + mNotifListContainer, + mHeadsUpManager, + mJankMonitor); + mNotificationShelfController.setOnActivatedListener(mPresenter); + mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController); + mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter); + mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); + mNotificationsController.initialize( + mPresenter, + mNotifListContainer, + mStackScrollerController.getNotifStackController(), + mNotificationActivityStarter, + mCentralSurfacesComponent.getBindRowCallback()); + } + + /** + * Post-init task of {@link #start()} + * @param state1 disable1 flags + * @param state2 disable2 flags + */ + protected void setUpDisableFlags(int state1, int state2) { + mCommandQueue.disable(mDisplayId, state1, state2, false /* animate */); + } + + /** + * Ask the display to wake up if currently dozing, else do nothing + * + * @param time when to wake up + * @param where the view requesting the wakeup + * @param why the reason for the wake up + */ + @Override + public void wakeUpIfDozing(long time, View where, String why) { + if (mDozing && mScreenOffAnimationController.allowWakeUpIfDozing()) { + mPowerManager.wakeUp( + time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why); + mWakeUpComingFromTouch = true; + where.getLocationInWindow(mTmpInt2); + + // NOTE, the incoming view can sometimes be the entire container... unsure if + // this location is valuable enough + mWakeUpTouchLocation = new PointF(mTmpInt2[0] + where.getWidth() / 2, + mTmpInt2[1] + where.getHeight() / 2); + mFalsingCollector.onScreenOnFromTouch(); + } + } + + // TODO(b/117478341): This was left such that CarStatusBar can override this method. + // Try to remove this. + protected void createNavigationBar(@Nullable RegisterStatusBarResult result) { + mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result); + } + + /** + * Returns the {@link android.view.View.OnTouchListener} that will be invoked when the + * background window of the status bar is clicked. + */ + protected View.OnTouchListener getStatusBarWindowTouchListener() { + return (v, event) -> { + mAutoHideController.checkUserAutoHide(event); + mRemoteInputManager.checkRemoteInputOutside(event); + if (event.getAction() == MotionEvent.ACTION_UP) { + if (mExpandedVisible) { + mShadeController.animateCollapsePanels(); + } + } + return mNotificationShadeWindowView.onTouchEvent(event); + }; + } + + private void inflateStatusBarWindow() { + if (mCentralSurfacesComponent != null) { + // Tear down + for (CentralSurfacesComponent.Startable s : mCentralSurfacesComponent.getStartables()) { + s.stop(); + } + } + mCentralSurfacesComponent = mCentralSurfacesComponentFactory.create(); + mFragmentService.addFragmentInstantiationProvider(mCentralSurfacesComponent); + + mNotificationShadeWindowView = mCentralSurfacesComponent.getNotificationShadeWindowView(); + mNotificationShadeWindowViewController = mCentralSurfacesComponent + .getNotificationShadeWindowViewController(); + mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); + mNotificationShadeWindowViewController.setupExpandedStatusBar(); + mNotificationPanelViewController = + mCentralSurfacesComponent.getNotificationPanelViewController(); + mCentralSurfacesComponent.getLockIconViewController().init(); + mStackScrollerController = + mCentralSurfacesComponent.getNotificationStackScrollLayoutController(); + mStackScroller = mStackScrollerController.getView(); + mNotifListContainer = mCentralSurfacesComponent.getNotificationListContainer(); + mPresenter = mCentralSurfacesComponent.getNotificationPresenter(); + mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter(); + mNotificationShelfController = mCentralSurfacesComponent.getNotificationShelfController(); + mAuthRippleController = mCentralSurfacesComponent.getAuthRippleController(); + mAuthRippleController.init(); + + mHeadsUpManager.addListener(mCentralSurfacesComponent.getStatusBarHeadsUpChangeListener()); + + // Listen for demo mode changes + mDemoModeController.addCallback(mDemoModeCallback); + + if (mCommandQueueCallbacks != null) { + mCommandQueue.removeCallback(mCommandQueueCallbacks); + } + mCommandQueueCallbacks = + mCentralSurfacesComponent.getCentralSurfacesCommandQueueCallbacks(); + // Connect in to the status bar manager service + mCommandQueue.addCallback(mCommandQueueCallbacks); + + // Perform all other initialization for CentralSurfacesScope + for (CentralSurfacesComponent.Startable s : mCentralSurfacesComponent.getStartables()) { + s.start(); + } + } + + protected void startKeyguard() { + Trace.beginSection("CentralSurfaces#startKeyguard"); + mBiometricUnlockController = mBiometricUnlockControllerLazy.get(); + mBiometricUnlockController.setBiometricModeListener( + new BiometricUnlockController.BiometricModeListener() { + @Override + public void onResetMode() { + setWakeAndUnlocking(false); + } + + @Override + public void onModeChanged(int mode) { + switch (mode) { + case BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM: + case BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING: + case BiometricUnlockController.MODE_WAKE_AND_UNLOCK: + setWakeAndUnlocking(true); + } + } + + @Override + public void notifyBiometricAuthModeChanged() { + CentralSurfacesImpl.this.notifyBiometricAuthModeChanged(); + } + + private void setWakeAndUnlocking(boolean wakeAndUnlocking) { + if (getNavigationBarView() != null) { + getNavigationBarView().setWakeAndUnlocking(wakeAndUnlocking); + } + } + }); + mStatusBarKeyguardViewManager.registerCentralSurfaces( + /* statusBar= */ this, + mNotificationPanelViewController, + mPanelExpansionStateManager, + mBiometricUnlockController, + mStackScroller, + mKeyguardBypassController); + mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); + mKeyguardIndicationController + .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); + mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); + mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager); + mDynamicPrivacyController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); + + mLightBarController.setBiometricUnlockController(mBiometricUnlockController); + mMediaManager.setBiometricUnlockController(mBiometricUnlockController); + mKeyguardDismissUtil.setDismissHandler(this::executeWhenUnlocked); + Trace.endSection(); + } + + @Override + public NotificationShadeWindowView getNotificationShadeWindowView() { + return mNotificationShadeWindowView; + } + + @Override + public NotificationShadeWindowViewController getNotificationShadeWindowViewController() { + return mNotificationShadeWindowViewController; + } + + @Override + public NotificationPanelViewController getNotificationPanelViewController() { + return mNotificationPanelViewController; + } + + @Override + public ViewGroup getBouncerContainer() { + return mNotificationShadeWindowViewController.getBouncerContainer(); + } + + @Override + public int getStatusBarHeight() { + return mStatusBarWindowController.getStatusBarHeight(); + } + + /** + * Disable QS if device not provisioned. + * If the user switcher is simple then disable QS during setup because + * the user intends to use the lock screen user switcher, QS in not needed. + */ + @Override + public void updateQsExpansionEnabled() { + final boolean expandEnabled = mDeviceProvisionedController.isDeviceProvisioned() + && (mUserSetup || mUserSwitcherController == null + || !mUserSwitcherController.isSimpleUserSwitcher()) + && !isShadeDisabled() + && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0) + && !mDozing + && !ONLY_CORE_APPS; + mNotificationPanelViewController.setQsExpansionEnabledPolicy(expandEnabled); + Log.d(TAG, "updateQsExpansionEnabled - QS Expand enabled: " + expandEnabled); + } + + @Override + public boolean isShadeDisabled() { + return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0; + } + + /** + * Request a notification update + * @param reason why we're requesting a notification update + */ + @Override + public void requestNotificationUpdate(String reason) { + mNotificationsController.requestNotificationUpdate(reason); + } + + /** + * Asks {@link KeyguardUpdateMonitor} to run face auth. + */ + @Override + public void requestFaceAuth(boolean userInitiatedRequest) { + if (!mKeyguardStateController.canDismissLockScreen()) { + mKeyguardUpdateMonitor.requestFaceAuth(userInitiatedRequest); + } + } + + private void updateReportRejectedTouchVisibility() { + if (mReportRejectedTouch == null) { + return; + } + mReportRejectedTouch.setVisibility(mState == StatusBarState.KEYGUARD && !mDozing + && mFalsingCollector.isReportingEnabled() ? View.VISIBLE : View.INVISIBLE); + } + + @Override + public boolean areNotificationAlertsDisabled() { + return (mDisabled1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; + } + + @Override + public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, + int flags) { + startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, flags); + } + + @Override + public void startActivity(Intent intent, boolean dismissShade) { + startActivityDismissingKeyguard(intent, false /* onlyProvisioned */, dismissShade); + } + + @Override + public void startActivity(Intent intent, boolean dismissShade, + @Nullable ActivityLaunchAnimator.Controller animationController, + boolean showOverLockscreenWhenLocked) { + startActivity(intent, dismissShade, animationController, showOverLockscreenWhenLocked, + getActivityUserHandle(intent)); + } + + @Override + public void startActivity(Intent intent, boolean dismissShade, + @Nullable ActivityLaunchAnimator.Controller animationController, + boolean showOverLockscreenWhenLocked, UserHandle userHandle) { + // Make sure that we dismiss the keyguard if it is directly dismissable or when we don't + // want to show the activity above it. + if (mKeyguardStateController.isUnlocked() || !showOverLockscreenWhenLocked) { + startActivityDismissingKeyguard(intent, false, dismissShade, + false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, + 0 /* flags */, animationController, userHandle); + return; + } + + boolean animate = + animationController != null && shouldAnimateLaunch(true /* isActivityIntent */, + showOverLockscreenWhenLocked); + + ActivityLaunchAnimator.Controller controller = null; + if (animate) { + // Wrap the animation controller to dismiss the shade and set + // mIsLaunchingActivityOverLockscreen during the animation. + ActivityLaunchAnimator.Controller delegate = wrapAnimationController( + animationController, dismissShade); + controller = new DelegateLaunchAnimatorController(delegate) { + @Override + public void onIntentStarted(boolean willAnimate) { + getDelegate().onIntentStarted(willAnimate); + + if (willAnimate) { + CentralSurfacesImpl.this.mIsLaunchingActivityOverLockscreen = true; + } + } + + @Override + public void onLaunchAnimationStart(boolean isExpandingFullyAbove) { + super.onLaunchAnimationStart(isExpandingFullyAbove); + + // Double check that the keyguard is still showing and not going away, but if so + // set the keyguard occluded. Typically, WM will let KeyguardViewMediator know + // directly, but we're overriding that to play the custom launch animation, so + // we need to take care of that here. The unocclude animation is not overridden, + // so WM will call KeyguardViewMediator's unocclude animation runner when the + // activity is exited. + if (mKeyguardStateController.isShowing() + && !mKeyguardStateController.isKeyguardGoingAway()) { + mKeyguardViewMediator.setOccluded(true /* isOccluded */, + true /* animate */); + } + } + + @Override + public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) { + // Set mIsLaunchingActivityOverLockscreen to false before actually finishing the + // animation so that we can assume that mIsLaunchingActivityOverLockscreen + // being true means that we will collapse the shade (or at least run the + // post collapse runnables) later on. + CentralSurfacesImpl.this.mIsLaunchingActivityOverLockscreen = false; + getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove); + } + + @Override + public void onLaunchAnimationCancelled() { + // Set mIsLaunchingActivityOverLockscreen to false before actually finishing the + // animation so that we can assume that mIsLaunchingActivityOverLockscreen + // being true means that we will collapse the shade (or at least run the + // post collapse runnables) later on. + CentralSurfacesImpl.this.mIsLaunchingActivityOverLockscreen = false; + getDelegate().onLaunchAnimationCancelled(); + } + }; + } else if (dismissShade) { + // The animation will take care of dismissing the shade at the end of the animation. If + // we don't animate, collapse it directly. + collapseShade(); + } + + mActivityLaunchAnimator.startIntentWithAnimation(controller, animate, + intent.getPackage(), showOverLockscreenWhenLocked, (adapter) -> TaskStackBuilder + .create(mContext) + .addNextIntent(intent) + .startActivities( + CentralSurfaces.getActivityOptions(getDisplayId(), adapter), + userHandle)); + } + + /** + * Whether we are currently animating an activity launch above the lockscreen (occluding + * activity). + */ + @Override + public boolean isLaunchingActivityOverLockscreen() { + return mIsLaunchingActivityOverLockscreen; + } + + @Override + public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade) { + startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade); + } + + @Override + public void startActivity(Intent intent, boolean dismissShade, Callback callback) { + startActivityDismissingKeyguard(intent, false, dismissShade, + false /* disallowEnterPictureInPictureWhileLaunching */, callback, 0, + null /* animationController */, getActivityUserHandle(intent)); + } + + @Override + public void setQsExpanded(boolean expanded) { + mNotificationShadeWindowController.setQsExpanded(expanded); + mNotificationPanelViewController.setStatusAccessibilityImportance(expanded + ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); + mNotificationPanelViewController.updateSystemUiStateFlags(); + if (getNavigationBarView() != null) { + getNavigationBarView().onStatusBarPanelStateChanged(); + } + } + + @Override + public boolean isWakeUpComingFromTouch() { + return mWakeUpComingFromTouch; + } + + @Override + public boolean isFalsingThresholdNeeded() { + return true; + } + + /** + * To be called when there's a state change in StatusBarKeyguardViewManager. + */ + @Override + public void onKeyguardViewManagerStatesUpdated() { + logStateToEventlog(); + } + + @Override + public void setPanelExpanded(boolean isExpanded) { + if (mPanelExpanded != isExpanded) { + mNotificationLogger.onPanelExpandedChanged(isExpanded); + } + mPanelExpanded = isExpanded; + mStatusBarHideIconsForBouncerManager.setPanelExpandedAndTriggerUpdate(isExpanded); + mNotificationShadeWindowController.setPanelExpanded(isExpanded); + mStatusBarStateController.setPanelExpanded(isExpanded); + if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) { + if (DEBUG) { + Log.v(TAG, "clearing notification effects from Height"); + } + clearNotificationEffects(); + } + + if (!isExpanded) { + mRemoteInputManager.onPanelCollapsed(); + } + } + + @Override + public ViewGroup getNotificationScrollLayout() { + return mStackScroller; + } + + @Override + public boolean isPulsing() { + return mDozeServiceHost.isPulsing(); + } + + @androidx.annotation.Nullable + @Override + public View getAmbientIndicationContainer() { + return mAmbientIndicationContainer; + } + + /** + * When the keyguard is showing and covered by a "showWhenLocked" activity it + * is occluded. This is controlled by {@link com.android.server.policy.PhoneWindowManager} + * + * @return whether the keyguard is currently occluded + */ + @Override + public boolean isOccluded() { + return mKeyguardStateController.isOccluded(); + } + + /** A launch animation was cancelled. */ + //TODO: These can / should probably be moved to NotificationPresenter or ShadeController + @Override + public void onLaunchAnimationCancelled(boolean isLaunchForActivity) { + if (mPresenter.isPresenterFullyCollapsed() && !mPresenter.isCollapsing() + && isLaunchForActivity) { + onClosingFinished(); + } else { + mShadeController.collapsePanel(true /* animate */); + } + } + + /** A launch animation ended. */ + @Override + public void onLaunchAnimationEnd(boolean launchIsFullScreen) { + if (!mPresenter.isCollapsing()) { + onClosingFinished(); + } + if (launchIsFullScreen) { + instantCollapseNotificationPanel(); + } + } + + /** + * Whether we should animate an activity launch. + * + * Note: This method must be called *before* dismissing the keyguard. + */ + @Override + public boolean shouldAnimateLaunch(boolean isActivityIntent, boolean showOverLockscreen) { + // TODO(b/184121838): Support launch animations when occluded. + if (isOccluded()) { + return false; + } + + // Always animate if we are not showing the keyguard or if we animate over the lockscreen + // (without unlocking it). + if (showOverLockscreen || !mKeyguardStateController.isShowing()) { + return true; + } + + // If we are locked and have to dismiss the keyguard, only animate if remote unlock + // animations are enabled. We also don't animate non-activity launches as they can break the + // animation. + // TODO(b/184121838): Support non activity launches on the lockscreen. + return isActivityIntent && KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation; + } + + /** Whether we should animate an activity launch. */ + @Override + public boolean shouldAnimateLaunch(boolean isActivityIntent) { + return shouldAnimateLaunch(isActivityIntent, false /* showOverLockscreen */); + } + + @Override + public boolean isDeviceInVrMode() { + return mPresenter.isDeviceInVrMode(); + } + + @Override + public NotificationPresenter getPresenter() { + return mPresenter; + } + + @VisibleForTesting + @Override + public void setBarStateForTest(int state) { + mState = state; + } + + static class AnimateExpandSettingsPanelMessage { + final String mSubpanel; + + AnimateExpandSettingsPanelMessage(String subpanel) { + mSubpanel = subpanel; + } + } + + private void maybeEscalateHeadsUp() { + mHeadsUpManager.getAllEntries().forEach(entry -> { + final StatusBarNotification sbn = entry.getSbn(); + final Notification notification = sbn.getNotification(); + if (notification.fullScreenIntent != null) { + if (DEBUG) { + Log.d(TAG, "converting a heads up to fullScreen"); + } + try { + EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION, + sbn.getKey()); + wakeUpForFullScreenIntent(); + notification.fullScreenIntent.send(); + entry.notifyFullScreenIntentLaunched(); + } catch (PendingIntent.CanceledException e) { + } + } + }); + mHeadsUpManager.releaseAllImmediately(); + } + + @Override + public void wakeUpForFullScreenIntent() { + if (isGoingToSleep() || mDozing) { + mPowerManager.wakeUp( + SystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_APPLICATION, + "com.android.systemui:full_screen_intent"); + mWakeUpComingFromTouch = false; + mWakeUpTouchLocation = null; + } + } + + @Override + public void makeExpandedVisible(boolean force) { + if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); + if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) { + return; + } + + mExpandedVisible = true; + + // Expand the window to encompass the full screen in anticipation of the drag. + // This is only possible to do atomically because the status bar is at the top of the screen! + mNotificationShadeWindowController.setPanelVisible(true); + + visibilityChanged(true); + mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */); + setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true); + } + + @Override + public void postAnimateCollapsePanels() { + mMainExecutor.execute(mShadeController::animateCollapsePanels); + } + + @Override + public void postAnimateForceCollapsePanels() { + mMainExecutor.execute( + () -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, + true /* force */)); + } + + @Override + public void postAnimateOpenPanels() { + mMessageRouter.sendMessage(MSG_OPEN_SETTINGS_PANEL); + } + + @Override + public boolean isExpandedVisible() { + return mExpandedVisible; + } + + @Override + public boolean isPanelExpanded() { + return mPanelExpanded; + } + + /** + * Called when another window is about to transfer it's input focus. + */ + @Override + public void onInputFocusTransfer(boolean start, boolean cancel, float velocity) { + if (!mCommandQueue.panelsEnabled()) { + return; + } + + if (start) { + mNotificationPanelViewController.startWaitingForOpenPanelGesture(); + } else { + mNotificationPanelViewController.stopWaitingForOpenPanelGesture(cancel, velocity); + } + } + + @Override + public void animateCollapseQuickSettings() { + if (mState == StatusBarState.SHADE) { + mNotificationPanelViewController.collapsePanel( + true, false /* delayed */, 1.0f /* speedUpFactor */); + } + } + + void makeExpandedInvisible() { + if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible + + " mExpandedVisible=" + mExpandedVisible); + + if (!mExpandedVisible || mNotificationShadeWindowView == null) { + return; + } + + // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868) + mNotificationPanelViewController.collapsePanel(/*animate=*/ false, false /* delayed*/, + 1.0f /* speedUpFactor */); + + mNotificationPanelViewController.closeQs(); + + mExpandedVisible = false; + visibilityChanged(false); + + // Update the visibility of notification shade and status bar window. + mNotificationShadeWindowController.setPanelVisible(false); + mStatusBarWindowController.setForceStatusBarVisible(false); + + // Close any guts that might be visible + mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, + true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */); + + mShadeController.runPostCollapseRunnables(); + setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); + if (!mNotificationActivityStarter.isCollapsingToShowActivityOverLockscreen()) { + showBouncerOrLockScreenIfKeyguard(); + } else if (DEBUG) { + Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen"); + } + mCommandQueue.recomputeDisableFlags( + mDisplayId, + mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */); + + // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in + // the bouncer appear animation. + if (!mStatusBarKeyguardViewManager.isShowing()) { + WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); + } + } + + /** Called when a touch event occurred on {@link PhoneStatusBarView}. */ + @Override + public void onTouchEvent(MotionEvent event) { + // TODO(b/202981994): Move this touch debugging to a central location. (Right now, it's + // split between NotificationPanelViewController and here.) + if (DEBUG_GESTURES) { + if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { + EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH, + event.getActionMasked(), (int) event.getX(), (int) event.getY(), + mDisabled1, mDisabled2); + } + + } + + if (SPEW) { + Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1=" + + mDisabled1 + " mDisabled2=" + mDisabled2); + } else if (CHATTY) { + if (event.getAction() != MotionEvent.ACTION_MOVE) { + Log.d(TAG, String.format( + "panel: %s at (%f, %f) mDisabled1=0x%08x mDisabled2=0x%08x", + MotionEvent.actionToString(event.getAction()), + event.getRawX(), event.getRawY(), mDisabled1, mDisabled2)); + } + } + + if (DEBUG_GESTURES) { + mGestureRec.add(event); + } + + if (mStatusBarWindowState == WINDOW_STATE_SHOWING) { + final boolean upOrCancel = + event.getAction() == MotionEvent.ACTION_UP || + event.getAction() == MotionEvent.ACTION_CANCEL; + setInteracting(StatusBarManager.WINDOW_STATUS_BAR, !upOrCancel || mExpandedVisible); + } + } + + @Override + public GestureRecorder getGestureRecorder() { + return mGestureRec; + } + + @Override + public BiometricUnlockController getBiometricUnlockController() { + return mBiometricUnlockController; + } + + @Override + public void showTransientUnchecked() { + if (!mTransientShown) { + mTransientShown = true; + mNoAnimationOnNextBarModeChange = true; + maybeUpdateBarMode(); + } + } + + @Override + public void clearTransient() { + if (mTransientShown) { + mTransientShown = false; + maybeUpdateBarMode(); + } + } + + private void maybeUpdateBarMode() { + final int barMode = barMode(mTransientShown, mAppearance); + if (updateBarMode(barMode)) { + mLightBarController.onStatusBarModeChanged(barMode); + updateBubblesVisibility(); + } + } + + private boolean updateBarMode(int barMode) { + if (mStatusBarMode != barMode) { + mStatusBarMode = barMode; + checkBarModes(); + mAutoHideController.touchAutoHide(); + return true; + } + return false; + } + + private @TransitionMode int barMode(boolean isTransient, int appearance) { + final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_STATUS_BARS; + if (mOngoingCallController.hasOngoingCall() && mIsFullscreen) { + return MODE_SEMI_TRANSPARENT; + } else if (isTransient) { + return MODE_SEMI_TRANSPARENT; + } else if ((appearance & lightsOutOpaque) == lightsOutOpaque) { + return MODE_LIGHTS_OUT; + } else if ((appearance & APPEARANCE_LOW_PROFILE_BARS) != 0) { + return MODE_LIGHTS_OUT_TRANSPARENT; + } else if ((appearance & APPEARANCE_OPAQUE_STATUS_BARS) != 0) { + return MODE_OPAQUE; + } else if ((appearance & APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS) != 0) { + return MODE_SEMI_TRANSPARENT; + } else { + return MODE_TRANSPARENT; + } + } + + @Override + public void showWirelessChargingAnimation(int batteryLevel) { + showChargingAnimation(batteryLevel, UNKNOWN_BATTERY_LEVEL, 0); + } + + protected void showChargingAnimation(int batteryLevel, int transmittingBatteryLevel, + long animationDelay) { + WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null, + transmittingBatteryLevel, batteryLevel, + new WirelessChargingAnimation.Callback() { + @Override + public void onAnimationStarting() { + mNotificationShadeWindowController.setRequestTopUi(true, TAG); + } + + @Override + public void onAnimationEnded() { + mNotificationShadeWindowController.setRequestTopUi(false, TAG); + } + }, false, sUiEventLogger).show(animationDelay); + } + + @Override + public void checkBarModes() { + if (mDemoModeController.isInDemoMode()) return; + if (mStatusBarTransitions != null) { + checkBarMode(mStatusBarMode, mStatusBarWindowState, mStatusBarTransitions); + } + mNavigationBarController.checkNavBarModes(mDisplayId); + mNoAnimationOnNextBarModeChange = false; + } + + // Called by NavigationBarFragment + @Override + public void setQsScrimEnabled(boolean scrimEnabled) { + mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled); + } + + /** Temporarily hides Bubbles if the status bar is hidden. */ + @Override + public void updateBubblesVisibility() { + mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged( + mStatusBarMode != MODE_LIGHTS_OUT + && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT + && !mStatusBarWindowHidden)); + } + + void checkBarMode(@TransitionMode int mode, @WindowVisibleState int windowState, + BarTransitions transitions) { + final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive + && windowState != WINDOW_STATE_HIDDEN; + transitions.transitionTo(mode, anim); + } + + private void finishBarAnimations() { + if (mStatusBarTransitions != null) { + mStatusBarTransitions.finishAnimations(); + } + mNavigationBarController.finishBarAnimations(mDisplayId); + } + + private final Runnable mCheckBarModes = this::checkBarModes; + + @Override + public void setInteracting(int barWindow, boolean interacting) { + mInteractingWindows = interacting + ? (mInteractingWindows | barWindow) + : (mInteractingWindows & ~barWindow); + if (mInteractingWindows != 0) { + mAutoHideController.suspendAutoHide(); + } else { + mAutoHideController.resumeSuspendedAutoHide(); + } + checkBarModes(); + } + + private void dismissVolumeDialog() { + if (mVolumeComponent != null) { + mVolumeComponent.dismissNow(); + } + } + + @Override + public void dump(PrintWriter pwOriginal, String[] args) { + IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); + synchronized (mQueueLock) { + pw.println("Current Status Bar state:"); + pw.println(" mExpandedVisible=" + mExpandedVisible); + pw.println(" mDisplayMetrics=" + mDisplayMetrics); + pw.println(" mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller)); + pw.println(" mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller) + + " scroll " + mStackScroller.getScrollX() + + "," + mStackScroller.getScrollY()); + } + + pw.print(" mInteractingWindows="); pw.println(mInteractingWindows); + pw.print(" mStatusBarWindowState="); + pw.println(windowStateToString(mStatusBarWindowState)); + pw.print(" mStatusBarMode="); + pw.println(BarTransitions.modeToString(mStatusBarMode)); + pw.print(" mDozing="); pw.println(mDozing); + pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported); + + pw.println(" ShadeWindowView: "); + if (mNotificationShadeWindowViewController != null) { + mNotificationShadeWindowViewController.dump(pw, args); + CentralSurfaces.dumpBarTransitions( + pw, "PhoneStatusBarTransitions", mStatusBarTransitions); + } + + pw.println(" mMediaManager: "); + if (mMediaManager != null) { + mMediaManager.dump(pw, args); + } + + pw.println(" Panels: "); + if (mNotificationPanelViewController != null) { + pw.println(" mNotificationPanel=" + + mNotificationPanelViewController.getView() + " params=" + + mNotificationPanelViewController.getView().getLayoutParams().debug("")); + pw.print (" "); + mNotificationPanelViewController.dump(pw, args); + } + pw.println(" mStackScroller: "); + if (mStackScroller != null) { + // Double indent until we rewrite the rest of this dump() + pw.increaseIndent(); + pw.increaseIndent(); + mStackScroller.dump(pw, args); + pw.decreaseIndent(); + pw.decreaseIndent(); + } + pw.println(" Theme:"); + String nightMode = mUiModeManager == null ? "null" : mUiModeManager.getNightMode() + ""; + pw.println(" dark theme: " + nightMode + + " (auto: " + UiModeManager.MODE_NIGHT_AUTO + + ", yes: " + UiModeManager.MODE_NIGHT_YES + + ", no: " + UiModeManager.MODE_NIGHT_NO + ")"); + final boolean lightWpTheme = mContext.getThemeResId() + == R.style.Theme_SystemUI_LightWallpaper; + pw.println(" light wallpaper theme: " + lightWpTheme); + + if (mKeyguardIndicationController != null) { + mKeyguardIndicationController.dump(pw, args); + } + + if (mScrimController != null) { + mScrimController.dump(pw, args); + } + + if (mLightRevealScrim != null) { + pw.println( + "mLightRevealScrim.getRevealEffect(): " + mLightRevealScrim.getRevealEffect()); + pw.println( + "mLightRevealScrim.getRevealAmount(): " + mLightRevealScrim.getRevealAmount()); + } + + if (mStatusBarKeyguardViewManager != null) { + mStatusBarKeyguardViewManager.dump(pw); + } + + mNotificationsController.dump(pw, args, DUMPTRUCK); + + if (DEBUG_GESTURES) { + pw.print(" status bar gestures: "); + mGestureRec.dump(pw, args); + } + + if (mHeadsUpManager != null) { + mHeadsUpManager.dump(pw, args); + } else { + pw.println(" mHeadsUpManager: null"); + } + + if (mStatusBarTouchableRegionManager != null) { + mStatusBarTouchableRegionManager.dump(pw, args); + } else { + pw.println(" mStatusBarTouchableRegionManager: null"); + } + + if (mLightBarController != null) { + mLightBarController.dump(pw, args); + } + + pw.println("SharedPreferences:"); + for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) { + pw.print(" "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue()); + } + + pw.println("Camera gesture intents:"); + pw.println(" Insecure camera: " + CameraIntents.getInsecureCameraIntent(mContext)); + pw.println(" Secure camera: " + CameraIntents.getSecureCameraIntent(mContext)); + pw.println(" Override package: " + + CameraIntents.getOverrideCameraPackage(mContext)); + } + + @Override + public void createAndAddWindows(@Nullable RegisterStatusBarResult result) { + makeStatusBarView(result); + mNotificationShadeWindowController.attach(); + mStatusBarWindowController.attach(); + } + + // called by makeStatusbar and also by PhoneStatusBarView + void updateDisplaySize() { + mDisplay.getMetrics(mDisplayMetrics); + mDisplay.getSize(mCurrentDisplaySize); + if (DEBUG_GESTURES) { + mGestureRec.tag("display", + String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); + } + } + + @Override + public float getDisplayDensity() { + return mDisplayMetrics.density; + } + + @Override + public float getDisplayWidth() { + return mDisplayMetrics.widthPixels; + } + + @Override + public float getDisplayHeight() { + return mDisplayMetrics.heightPixels; + } + + @Override + public int getRotation() { + return mDisplay.getRotation(); + } + + @Override + public int getDisplayId() { + return mDisplayId; + } + + @Override + public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, + boolean dismissShade, int flags) { + startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, + false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, + flags, null /* animationController */, getActivityUserHandle(intent)); + } + + @Override + public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, + boolean dismissShade) { + startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, 0); + } + + @Override + public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, + final boolean dismissShade, final boolean disallowEnterPictureInPictureWhileLaunching, + final Callback callback, int flags, + @Nullable ActivityLaunchAnimator.Controller animationController, + final UserHandle userHandle) { + if (onlyProvisioned && !mDeviceProvisionedController.isDeviceProvisioned()) return; + + final boolean willLaunchResolverActivity = + mActivityIntentHelper.wouldLaunchResolverActivity(intent, + mLockscreenUserManager.getCurrentUserId()); + + boolean animate = + animationController != null && !willLaunchResolverActivity && shouldAnimateLaunch( + true /* isActivityIntent */); + ActivityLaunchAnimator.Controller animController = + animationController != null ? wrapAnimationController(animationController, + dismissShade) : null; + + // If we animate, we will dismiss the shade only once the animation is done. This is taken + // care of by the StatusBarLaunchAnimationController. + boolean dismissShadeDirectly = dismissShade && animController == null; + + Runnable runnable = () -> { + mAssistManagerLazy.get().hideAssist(); + intent.setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.addFlags(flags); + int[] result = new int[]{ActivityManager.START_CANCELED}; + + mActivityLaunchAnimator.startIntentWithAnimation(animController, + animate, intent.getPackage(), (adapter) -> { + ActivityOptions options = new ActivityOptions( + CentralSurfaces.getActivityOptions(mDisplayId, adapter)); + options.setDisallowEnterPictureInPictureWhileLaunching( + disallowEnterPictureInPictureWhileLaunching); + if (CameraIntents.isInsecureCameraIntent(intent)) { + // Normally an activity will set it's requested rotation + // animation on its window. However when launching an activity + // causes the orientation to change this is too late. In these cases + // the default animation is used. This doesn't look good for + // the camera (as it rotates the camera contents out of sync + // with physical reality). So, we ask the WindowManager to + // force the crossfade animation if an orientation change + // happens to occur during the launch. + options.setRotationAnimationHint( + WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS); + } + if (Settings.Panel.ACTION_VOLUME.equals(intent.getAction())) { + // Settings Panel is implemented as activity(not a dialog), so + // underlying app is paused and may enter picture-in-picture mode + // as a result. + // So we need to disable picture-in-picture mode here + // if it is volume panel. + options.setDisallowEnterPictureInPictureWhileLaunching(true); + } + + try { + result[0] = ActivityTaskManager.getService().startActivityAsUser( + null, mContext.getBasePackageName(), + mContext.getAttributionTag(), + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, + options.toBundle(), userHandle.getIdentifier()); + } catch (RemoteException e) { + Log.w(TAG, "Unable to start activity", e); + } + return result[0]; + }); + + if (callback != null) { + callback.onActivityStarted(result[0]); + } + }; + Runnable cancelRunnable = () -> { + if (callback != null) { + callback.onActivityStarted(ActivityManager.START_CANCELED); + } + }; + executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShadeDirectly, + willLaunchResolverActivity, true /* deferred */, animate); + } + + @Nullable + private ActivityLaunchAnimator.Controller wrapAnimationController( + ActivityLaunchAnimator.Controller animationController, boolean dismissShade) { + View rootView = animationController.getLaunchContainer().getRootView(); + + Optional<ActivityLaunchAnimator.Controller> controllerFromStatusBar = + mStatusBarWindowController.wrapAnimationControllerIfInStatusBar( + rootView, animationController); + if (controllerFromStatusBar.isPresent()) { + return controllerFromStatusBar.get(); + } + + if (dismissShade) { + // If the view is not in the status bar, then we are animating a view in the shade. + // We have to make sure that we collapse it when the animation ends or is cancelled. + return new StatusBarLaunchAnimatorController(animationController, this, + true /* isLaunchForActivity */); + } + + return animationController; + } + + @Override + public void readyForKeyguardDone() { + mStatusBarKeyguardViewManager.readyForKeyguardDone(); + } + + @Override + public void executeRunnableDismissingKeyguard(final Runnable runnable, + final Runnable cancelAction, + final boolean dismissShade, + final boolean afterKeyguardGone, + final boolean deferred) { + executeRunnableDismissingKeyguard(runnable, cancelAction, dismissShade, afterKeyguardGone, + deferred, false /* willAnimateOnKeyguard */); + } + + @Override + public void executeRunnableDismissingKeyguard(final Runnable runnable, + final Runnable cancelAction, + final boolean dismissShade, + final boolean afterKeyguardGone, + final boolean deferred, + final boolean willAnimateOnKeyguard) { + OnDismissAction onDismissAction = new OnDismissAction() { + @Override + public boolean onDismiss() { + if (runnable != null) { + if (mStatusBarKeyguardViewManager.isShowing() + && mStatusBarKeyguardViewManager.isOccluded()) { + mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); + } else { + mMainExecutor.execute(runnable); + } + } + if (dismissShade) { + if (mExpandedVisible && !mBouncerShowing) { + mShadeController.animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, + true /* force */, true /* delayed*/); + } else { + + // Do it after DismissAction has been processed to conserve the needed + // ordering. + mMainExecutor.execute(mShadeController::runPostCollapseRunnables); + } + } else if (CentralSurfacesImpl.this.isInLaunchTransition() + && mNotificationPanelViewController.isLaunchTransitionFinished()) { + + // We are not dismissing the shade, but the launch transition is already + // finished, + // so nobody will call readyForKeyguardDone anymore. Post it such that + // keyguardDonePending gets called first. + mMainExecutor.execute(mStatusBarKeyguardViewManager::readyForKeyguardDone); + } + return deferred; + } + + @Override + public boolean willRunAnimationOnKeyguard() { + return willAnimateOnKeyguard; + } + }; + dismissKeyguardThenExecute(onDismissAction, cancelAction, afterKeyguardGone); + } + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Trace.beginSection("CentralSurfaces#onReceive"); + if (DEBUG) Log.v(TAG, "onReceive: " + intent); + String action = intent.getAction(); + String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { + KeyboardShortcuts.dismiss(); + mRemoteInputManager.closeRemoteInputs(); + if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) { + int flags = CommandQueue.FLAG_EXCLUDE_NONE; + if (reason != null) { + if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) { + flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL; + } + // Do not collapse notifications when starting dreaming if the notifications + // shade is used for the screen off animation. It might require expanded + // state for the scrims to be visible + if (reason.equals(SYSTEM_DIALOG_REASON_DREAM) + && mScreenOffAnimationController.shouldExpandNotifications()) { + flags |= CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL; + } + } + mShadeController.animateCollapsePanels(flags); + } + } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { + if (mNotificationShadeWindowController != null) { + mNotificationShadeWindowController.setNotTouchable(false); + } + finishBarAnimations(); + resetUserExpandedStates(); + } + Trace.endSection(); + } + }; + + private final BroadcastReceiver mDemoReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) Log.v(TAG, "onReceive: " + intent); + String action = intent.getAction(); + if (ACTION_FAKE_ARTWORK.equals(action)) { + if (DEBUG_MEDIA_FAKE_ARTWORK) { + mPresenter.updateMediaMetaData(true, true); + } + } + } + }; + + @Override + public void resetUserExpandedStates() { + mNotificationsController.resetUserExpandedStates(); + } + + private void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen, + boolean afterKeyguardGone) { + if (mStatusBarKeyguardViewManager.isShowing() && requiresShadeOpen) { + mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); + } + dismissKeyguardThenExecute(action, null /* cancelAction */, + afterKeyguardGone /* afterKeyguardGone */); + } + + protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) { + dismissKeyguardThenExecute(action, null /* cancelRunnable */, afterKeyguardGone); + } + + @Override + public void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction, + boolean afterKeyguardGone) { + if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP + && mKeyguardStateController.canDismissLockScreen() + && !mStatusBarStateController.leaveOpenOnKeyguardHide() + && mDozeServiceHost.isPulsing()) { + // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a pulse. + // TODO: Factor this transition out of BiometricUnlockController. + mBiometricUnlockController.startWakeAndUnlock( + BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING); + } + if (mStatusBarKeyguardViewManager.isShowing()) { + mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction, + afterKeyguardGone); + } else { + action.onDismiss(); + } + } + /** + * Notify the shade controller that the current user changed + * + * @param newUserId userId of the new user + */ + @Override + public void setLockscreenUser(int newUserId) { + if (mLockscreenWallpaper != null) { + mLockscreenWallpaper.setCurrentUser(newUserId); + } + mScrimController.setCurrentUser(newUserId); + if (mWallpaperSupported) { + mWallpaperChangedReceiver.onReceive(mContext, null); + } + } + + /** + * Reload some of our resources when the configuration changes. + * + * We don't reload everything when the configuration changes -- we probably + * should, but getting that smooth is tough. Someday we'll fix that. In the + * meantime, just update the things that we know change. + */ + void updateResources() { + // Update the quick setting tiles + if (mQSPanelController != null) { + mQSPanelController.updateResources(); + } + + if (mStatusBarWindowController != null) { + mStatusBarWindowController.refreshStatusBarHeight(); + } + + if (mNotificationPanelViewController != null) { + mNotificationPanelViewController.updateResources(); + } + if (mBrightnessMirrorController != null) { + mBrightnessMirrorController.updateResources(); + } + if (mStatusBarKeyguardViewManager != null) { + mStatusBarKeyguardViewManager.updateResources(); + } + + mPowerButtonReveal = new PowerButtonReveal(mContext.getResources().getDimensionPixelSize( + com.android.systemui.R.dimen.physical_power_button_center_screen_location_y)); + } + + // Visibility reporting + protected void handleVisibleToUserChanged(boolean visibleToUser) { + if (visibleToUser) { + handleVisibleToUserChangedImpl(visibleToUser); + mNotificationLogger.startNotificationLogging(); + } else { + mNotificationLogger.stopNotificationLogging(); + handleVisibleToUserChangedImpl(visibleToUser); + } + } + + // Visibility reporting + void handleVisibleToUserChangedImpl(boolean visibleToUser) { + if (visibleToUser) { + /* The LEDs are turned off when the notification panel is shown, even just a little bit. + * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do + * this. + */ + boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp(); + boolean clearNotificationEffects = + !mPresenter.isPresenterFullyCollapsed() && + (mState == StatusBarState.SHADE + || mState == StatusBarState.SHADE_LOCKED); + int notificationLoad = mNotificationsController.getActiveNotificationsCount(); + if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) { + notificationLoad = 1; + } + final int finalNotificationLoad = notificationLoad; + mUiBgExecutor.execute(() -> { + try { + mBarService.onPanelRevealed(clearNotificationEffects, + finalNotificationLoad); + } catch (RemoteException ex) { + // Won't fail unless the world has ended. + } + }); + } else { + mUiBgExecutor.execute(() -> { + try { + mBarService.onPanelHidden(); + } catch (RemoteException ex) { + // Won't fail unless the world has ended. + } + }); + } + + } + + private void logStateToEventlog() { + boolean isShowing = mStatusBarKeyguardViewManager.isShowing(); + boolean isOccluded = mStatusBarKeyguardViewManager.isOccluded(); + boolean isBouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing(); + boolean isSecure = mKeyguardStateController.isMethodSecure(); + boolean unlocked = mKeyguardStateController.canDismissLockScreen(); + int stateFingerprint = getLoggingFingerprint(mState, + isShowing, + isOccluded, + isBouncerShowing, + isSecure, + unlocked); + if (stateFingerprint != mLastLoggedStateFingerprint) { + if (mStatusBarStateLog == null) { + mStatusBarStateLog = new LogMaker(MetricsEvent.VIEW_UNKNOWN); + } + mMetricsLogger.write(mStatusBarStateLog + .setCategory(isBouncerShowing ? MetricsEvent.BOUNCER : MetricsEvent.LOCKSCREEN) + .setType(isShowing ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE) + .setSubtype(isSecure ? 1 : 0)); + EventLogTags.writeSysuiStatusBarState(mState, + isShowing ? 1 : 0, + isOccluded ? 1 : 0, + isBouncerShowing ? 1 : 0, + isSecure ? 1 : 0, + unlocked ? 1 : 0); + mLastLoggedStateFingerprint = stateFingerprint; + + StringBuilder uiEventValueBuilder = new StringBuilder(); + uiEventValueBuilder.append(isBouncerShowing ? "BOUNCER" : "LOCKSCREEN"); + uiEventValueBuilder.append(isShowing ? "_OPEN" : "_CLOSE"); + uiEventValueBuilder.append(isSecure ? "_SECURE" : "_INSECURE"); + sUiEventLogger.log(StatusBarUiEvent.valueOf(uiEventValueBuilder.toString())); + } + } + + /** + * Returns a fingerprint of fields logged to eventlog + */ + private static int getLoggingFingerprint(int statusBarState, boolean keyguardShowing, + boolean keyguardOccluded, boolean bouncerShowing, boolean secure, + boolean currentlyInsecure) { + // Reserve 8 bits for statusBarState. We'll never go higher than + // that, right? Riiiight. + return (statusBarState & 0xFF) + | ((keyguardShowing ? 1 : 0) << 8) + | ((keyguardOccluded ? 1 : 0) << 9) + | ((bouncerShowing ? 1 : 0) << 10) + | ((secure ? 1 : 0) << 11) + | ((currentlyInsecure ? 1 : 0) << 12); + } + + @Override + public void postQSRunnableDismissingKeyguard(final Runnable runnable) { + mMainExecutor.execute(() -> { + mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); + executeRunnableDismissingKeyguard( + () -> mMainExecutor.execute(runnable), null, false, false, false); + }); + } + + @Override + public void postStartActivityDismissingKeyguard(PendingIntent intent) { + postStartActivityDismissingKeyguard(intent, null /* animationController */); + } + + @Override + public void postStartActivityDismissingKeyguard(final PendingIntent intent, + @Nullable ActivityLaunchAnimator.Controller animationController) { + mMainExecutor.execute(() -> startPendingIntentDismissingKeyguard(intent, + null /* intentSentUiThreadCallback */, animationController)); + } + + @Override + public void postStartActivityDismissingKeyguard(final Intent intent, int delay) { + postStartActivityDismissingKeyguard(intent, delay, null /* animationController */); + } + + @Override + public void postStartActivityDismissingKeyguard(Intent intent, int delay, + @Nullable ActivityLaunchAnimator.Controller animationController) { + mMainExecutor.executeDelayed( + () -> + startActivityDismissingKeyguard(intent, true /* onlyProvisioned */, + true /* dismissShade */, + false /* disallowEnterPictureInPictureWhileLaunching */, + null /* callback */, + 0 /* flags */, + animationController, + getActivityUserHandle(intent)), + delay); + } + + @Override + public void showKeyguard() { + mStatusBarStateController.setKeyguardRequested(true); + mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); + updateIsKeyguard(); + mAssistManagerLazy.get().onLockscreenShown(); + } + + @Override + public boolean hideKeyguard() { + mStatusBarStateController.setKeyguardRequested(false); + return updateIsKeyguard(); + } + + @Override + public boolean updateIsKeyguard() { + return updateIsKeyguard(false /* forceStateChange */); + } + + @Override + public boolean updateIsKeyguard(boolean forceStateChange) { + boolean wakeAndUnlocking = mBiometricUnlockController.isWakeAndUnlock(); + + // For dozing, keyguard needs to be shown whenever the device is non-interactive. Otherwise + // there's no surface we can show to the user. Note that the device goes fully interactive + // late in the transition, so we also allow the device to start dozing once the screen has + // turned off fully. + boolean keyguardForDozing = mDozeServiceHost.getDozingRequested() + && (!mDeviceInteractive || (isGoingToSleep() + && (isScreenFullyOff() + || (mKeyguardStateController.isShowing() && !isOccluded())))); + boolean isWakingAndOccluded = isOccluded() && isWakingOrAwake(); + boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested() + || keyguardForDozing) && !wakeAndUnlocking && !isWakingAndOccluded; + if (keyguardForDozing) { + updatePanelExpansionForKeyguard(); + } + if (shouldBeKeyguard) { + if (mScreenOffAnimationController.isKeyguardShowDelayed() + || (isGoingToSleep() + && mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_TURNING_OFF)) { + // Delay showing the keyguard until screen turned off. + } else { + showKeyguardImpl(); + } + } else { + // During folding a foldable device this might be called as a result of + // 'onScreenTurnedOff' call for the inner display. + // In this case: + // * When phone is locked on folding: it doesn't make sense to hide keyguard as it + // will be immediately locked again + // * When phone is unlocked: we still don't want to execute hiding of the keyguard + // as the animation could prepare 'fake AOD' interface (without actually + // transitioning to keyguard state) and this might reset the view states + if (!mScreenOffAnimationController.isKeyguardHideDelayed()) { + return hideKeyguardImpl(forceStateChange); + } + } + return false; + } + + @Override + public void showKeyguardImpl() { + Trace.beginSection("CentralSurfaces#showKeyguard"); + if (mKeyguardStateController.isLaunchTransitionFadingAway()) { + mNotificationPanelViewController.cancelAnimation(); + onLaunchTransitionFadingEnded(); + } + mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); + if (!mLockscreenShadeTransitionController.isWakingToShadeLocked()) { + mStatusBarStateController.setState(StatusBarState.KEYGUARD); + } + updatePanelExpansionForKeyguard(); + Trace.endSection(); + } + + private void updatePanelExpansionForKeyguard() { + if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode() + != BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) { + mShadeController.instantExpandNotificationsPanel(); + } + } + + private void onLaunchTransitionFadingEnded() { + mNotificationPanelViewController.resetAlpha(); + mNotificationPanelViewController.onAffordanceLaunchEnded(); + releaseGestureWakeLock(); + runLaunchTransitionEndRunnable(); + mKeyguardStateController.setLaunchTransitionFadingAway(false); + mPresenter.updateMediaMetaData(true /* metaDataChanged */, true); + } + + @Override + public boolean isInLaunchTransition() { + return mNotificationPanelViewController.isLaunchTransitionRunning() + || mNotificationPanelViewController.isLaunchTransitionFinished(); + } + + /** + * Fades the content of the keyguard away after the launch transition is done. + * + * @param beforeFading the runnable to be run when the circle is fully expanded and the fading + * starts + * @param endRunnable the runnable to be run when the transition is done. Will not run + * if the transition is cancelled, instead cancelRunnable will run + * @param cancelRunnable the runnable to be run if the transition is cancelled + */ + @Override + public void fadeKeyguardAfterLaunchTransition(final Runnable beforeFading, + Runnable endRunnable, Runnable cancelRunnable) { + mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); + mLaunchTransitionEndRunnable = endRunnable; + mLaunchTransitionCancelRunnable = cancelRunnable; + Runnable hideRunnable = () -> { + mKeyguardStateController.setLaunchTransitionFadingAway(true); + if (beforeFading != null) { + beforeFading.run(); + } + updateScrimController(); + mPresenter.updateMediaMetaData(false, true); + mNotificationPanelViewController.resetAlpha(); + mNotificationPanelViewController.fadeOut( + FADE_KEYGUARD_START_DELAY, FADE_KEYGUARD_DURATION, + this::onLaunchTransitionFadingEnded); + mCommandQueue.appTransitionStarting(mDisplayId, SystemClock.uptimeMillis(), + LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true); + }; + if (mNotificationPanelViewController.isLaunchTransitionRunning()) { + mNotificationPanelViewController.setLaunchTransitionEndRunnable(hideRunnable); + } else { + hideRunnable.run(); + } + } + + private void cancelAfterLaunchTransitionRunnables() { + if (mLaunchTransitionCancelRunnable != null) { + mLaunchTransitionCancelRunnable.run(); + } + mLaunchTransitionEndRunnable = null; + mLaunchTransitionCancelRunnable = null; + mNotificationPanelViewController.setLaunchTransitionEndRunnable(null); + } + + /** + * Fades the content of the Keyguard while we are dozing and makes it invisible when finished + * fading. + */ + @Override + public void fadeKeyguardWhilePulsing() { + mNotificationPanelViewController.fadeOut(0, FADE_KEYGUARD_DURATION_PULSING, + ()-> { + hideKeyguard(); + mStatusBarKeyguardViewManager.onKeyguardFadedAway(); + }).start(); + } + + /** + * Plays the animation when an activity that was occluding Keyguard goes away. + */ + @Override + public void animateKeyguardUnoccluding() { + mNotificationPanelViewController.setExpandedFraction(0f); + mCommandQueueCallbacks.animateExpandNotificationsPanel(); + mScrimController.setUnocclusionAnimationRunning(true); + } + + /** + * Starts the timeout when we try to start the affordances on Keyguard. We usually rely that + * Keyguard goes away via fadeKeyguardAfterLaunchTransition, however, that might not happen + * because the launched app crashed or something else went wrong. + */ + @Override + public void startLaunchTransitionTimeout() { + mMessageRouter.sendMessageDelayed( + MSG_LAUNCH_TRANSITION_TIMEOUT, LAUNCH_TRANSITION_TIMEOUT_MS); + } + + private void onLaunchTransitionTimeout() { + Log.w(TAG, "Launch transition: Timeout!"); + mNotificationPanelViewController.onAffordanceLaunchEnded(); + releaseGestureWakeLock(); + mNotificationPanelViewController.resetViews(false /* animate */); + } + + private void runLaunchTransitionEndRunnable() { + mLaunchTransitionCancelRunnable = null; + if (mLaunchTransitionEndRunnable != null) { + Runnable r = mLaunchTransitionEndRunnable; + + // mLaunchTransitionEndRunnable might call showKeyguard, which would execute it again, + // which would lead to infinite recursion. Protect against it. + mLaunchTransitionEndRunnable = null; + r.run(); + } + } + + /** + * @return true if we would like to stay in the shade, false if it should go away entirely + */ + @Override + public boolean hideKeyguardImpl(boolean forceStateChange) { + Trace.beginSection("CentralSurfaces#hideKeyguard"); + boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide(); + int previousState = mStatusBarStateController.getState(); + if (!(mStatusBarStateController.setState(StatusBarState.SHADE, forceStateChange))) { + //TODO: StatusBarStateController should probably know about hiding the keyguard and + // notify listeners. + + // If the state didn't change, we may still need to update public mode + mLockscreenUserManager.updatePublicMode(); + } + if (mStatusBarStateController.leaveOpenOnKeyguardHide()) { + if (!mStatusBarStateController.isKeyguardRequested()) { + mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); + } + long delay = mKeyguardStateController.calculateGoingToFullShadeDelay(); + mLockscreenShadeTransitionController.onHideKeyguard(delay, previousState); + + // Disable layout transitions in navbar for this transition because the load is just + // too heavy for the CPU and GPU on any device. + mNavigationBarController.disableAnimationsDuringHide(mDisplayId, delay); + } else if (!mNotificationPanelViewController.isCollapsing()) { + instantCollapseNotificationPanel(); + } + + // Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile + // visibilities so next time we open the panel we know the correct height already. + if (mQSPanelController != null) { + mQSPanelController.refreshAllTiles(); + } + mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); + releaseGestureWakeLock(); + mNotificationPanelViewController.onAffordanceLaunchEnded(); + mNotificationPanelViewController.resetAlpha(); + mNotificationPanelViewController.resetTranslation(); + mNotificationPanelViewController.resetViewGroupFade(); + updateDozingState(); + updateScrimController(); + Trace.endSection(); + return staying; + } + + private void releaseGestureWakeLock() { + if (mGestureWakeLock.isHeld()) { + mGestureWakeLock.release(); + } + } + + /** + * Notifies the status bar that Keyguard is going away very soon. + */ + @Override + public void keyguardGoingAway() { + // Treat Keyguard exit animation as an app transition to achieve nice transition for status + // bar. + mKeyguardStateController.notifyKeyguardGoingAway(true); + mCommandQueue.appTransitionPending(mDisplayId, true /* forced */); + updateScrimController(); + } + + /** + * Notifies the status bar the Keyguard is fading away with the specified timings. + * @param startTime the start time of the animations in uptime millis + * @param delay the precalculated animation delay in milliseconds + * @param fadeoutDuration the duration of the exit animation, in milliseconds + * @param isBypassFading is this a fading away animation while bypassing + */ + @Override + public void setKeyguardFadingAway(long startTime, long delay, long fadeoutDuration, + boolean isBypassFading) { + mCommandQueue.appTransitionStarting(mDisplayId, startTime + fadeoutDuration + - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, + LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true); + mCommandQueue.recomputeDisableFlags(mDisplayId, fadeoutDuration > 0 /* animate */); + mCommandQueue.appTransitionStarting(mDisplayId, + startTime - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, + LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true); + mKeyguardStateController.notifyKeyguardFadingAway(delay, fadeoutDuration, isBypassFading); + } + + /** + * Notifies that the Keyguard fading away animation is done. + */ + @Override + public void finishKeyguardFadingAway() { + mKeyguardStateController.notifyKeyguardDoneFading(); + mScrimController.setExpansionAffectsAlpha(true); + + // If the device was re-locked while unlocking, we might have a pending lock that was + // delayed because the keyguard was in the middle of going away. + mKeyguardViewMediator.maybeHandlePendingLock(); + } + + /** + * Switches theme from light to dark and vice-versa. + */ + protected void updateTheme() { + // Set additional scrim only if the lock and system wallpaper are different to prevent + // applying the dimming effect twice. + mUiBgExecutor.execute(() -> { + float dimAmount = 0f; + if (mWallpaperManager.lockScreenWallpaperExists()) { + dimAmount = mWallpaperManager.getWallpaperDimAmount(); + } + final float scrimDimAmount = dimAmount; + mMainExecutor.execute(() -> { + mScrimController.setAdditionalScrimBehindAlphaKeyguard(scrimDimAmount); + mScrimController.applyCompositeAlphaOnScrimBehindKeyguard(); + }); + }); + + // Lock wallpaper defines the color of the majority of the views, hence we'll use it + // to set our default theme. + final boolean lockDarkText = mColorExtractor.getNeutralColors().supportsDarkText(); + final int themeResId = lockDarkText ? R.style.Theme_SystemUI_LightWallpaper + : R.style.Theme_SystemUI; + if (mContext.getThemeResId() != themeResId) { + mContext.setTheme(themeResId); + mConfigurationController.notifyThemeChanged(); + } + } + + private void updateDozingState() { + Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0); + Trace.beginSection("CentralSurfaces#updateDozingState"); + + boolean visibleNotOccluded = mStatusBarKeyguardViewManager.isShowing() + && !mStatusBarKeyguardViewManager.isOccluded(); + // If we're dozing and we'll be animating the screen off, the keyguard isn't currently + // visible but will be shortly for the animation, so we should proceed as if it's visible. + boolean visibleNotOccludedOrWillBe = + visibleNotOccluded || (mDozing && mDozeParameters.shouldDelayKeyguardShow()); + + boolean wakeAndUnlock = mBiometricUnlockController.getMode() + == BiometricUnlockController.MODE_WAKE_AND_UNLOCK; + boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock) + || (mDozing && mDozeParameters.shouldControlScreenOff() + && visibleNotOccludedOrWillBe); + + mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation); + updateQsExpansionEnabled(); + Trace.endSection(); + } + + @Override + public void userActivity() { + if (mState == StatusBarState.KEYGUARD) { + mKeyguardViewMediatorCallback.userActivity(); + } + } + + @Override + public boolean interceptMediaKey(KeyEvent event) { + return mState == StatusBarState.KEYGUARD + && mStatusBarKeyguardViewManager.interceptMediaKey(event); + } + + /** + * While IME is active and a BACK event is detected, check with + * {@link StatusBarKeyguardViewManager#dispatchBackKeyEventPreIme()} to see if the event + * should be handled before routing to IME, in order to prevent the user having to hit back + * twice to exit bouncer. + */ + @Override + public boolean dispatchKeyEventPreIme(KeyEvent event) { + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_BACK: + if (mState == StatusBarState.KEYGUARD + && mStatusBarKeyguardViewManager.dispatchBackKeyEventPreIme()) { + return onBackPressed(); + } + } + return false; + } + + protected boolean shouldUnlockOnMenuPressed() { + return mDeviceInteractive && mState != StatusBarState.SHADE + && mStatusBarKeyguardViewManager.shouldDismissOnMenuPressed(); + } + + @Override + public boolean onMenuPressed() { + if (shouldUnlockOnMenuPressed()) { + mShadeController.animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */); + return true; + } + return false; + } + + @Override + public void endAffordanceLaunch() { + releaseGestureWakeLock(); + mNotificationPanelViewController.onAffordanceLaunchEnded(); + } + + @Override + public boolean onBackPressed() { + boolean isScrimmedBouncer = mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED; + if (mStatusBarKeyguardViewManager.onBackPressed(isScrimmedBouncer /* hideImmediately */)) { + if (isScrimmedBouncer) { + mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); + } else { + mNotificationPanelViewController.expandWithoutQs(); + } + return true; + } + if (mNotificationPanelViewController.isQsCustomizing()) { + mNotificationPanelViewController.closeQsCustomizer(); + return true; + } + if (mNotificationPanelViewController.isQsExpanded()) { + if (mNotificationPanelViewController.isQsDetailShowing()) { + mNotificationPanelViewController.closeQsDetail(); + } else { + mNotificationPanelViewController.animateCloseQs(false /* animateAway */); + } + return true; + } + if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) { + return true; + } + if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) { + if (mNotificationPanelViewController.canPanelBeCollapsed()) { + mShadeController.animateCollapsePanels(); + } + return true; + } + return false; + } + + @Override + public boolean onSpacePressed() { + if (mDeviceInteractive && mState != StatusBarState.SHADE) { + mShadeController.animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */); + return true; + } + return false; + } + + private void showBouncerOrLockScreenIfKeyguard() { + // If the keyguard is animating away, we aren't really the keyguard anymore and should not + // show the bouncer/lockscreen. + if (!mKeyguardViewMediator.isHiding() + && !mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) { + if (mState == StatusBarState.SHADE_LOCKED + && mKeyguardUpdateMonitor.isUdfpsEnrolled()) { + // shade is showing while locked on the keyguard, so go back to showing the + // lock screen where users can use the UDFPS affordance to enter the device + mStatusBarKeyguardViewManager.reset(true); + } else if ((mState == StatusBarState.KEYGUARD + && !mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()) + || mState == StatusBarState.SHADE_LOCKED) { + mStatusBarKeyguardViewManager.showGenericBouncer(true /* scrimmed */); + } + } + } + + /** + * Show the bouncer if we're currently on the keyguard or shade locked and aren't hiding. + * @param performAction the action to perform when the bouncer is dismissed. + * @param cancelAction the action to perform when unlock is aborted. + */ + @Override + public void showBouncerWithDimissAndCancelIfKeyguard(OnDismissAction performAction, + Runnable cancelAction) { + if ((mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) + && !mKeyguardViewMediator.isHiding()) { + mStatusBarKeyguardViewManager.dismissWithAction(performAction, cancelAction, + false /* afterKeyguardGone */); + } else if (cancelAction != null) { + cancelAction.run(); + } + } + + @Override + public void instantCollapseNotificationPanel() { + mNotificationPanelViewController.instantCollapse(); + mShadeController.runPostCollapseRunnables(); + } + + /** + * Collapse the panel directly if we are on the main thread, post the collapsing on the main + * thread if we are not. + */ + @Override + public void collapsePanelOnMainThread() { + if (Looper.getMainLooper().isCurrentThread()) { + mShadeController.collapsePanel(); + } else { + mContext.getMainExecutor().execute(mShadeController::collapsePanel); + } + } + + /** Collapse the panel. The collapsing will be animated for the given {@code duration}. */ + @Override + public void collapsePanelWithDuration(int duration) { + mNotificationPanelViewController.collapseWithDuration(duration); + } + + /** + * Updates the light reveal effect to reflect the reason we're waking or sleeping (for example, + * from the power button). + * @param wakingUp Whether we're updating because we're waking up (true) or going to sleep + * (false). + */ + private void updateRevealEffect(boolean wakingUp) { + if (mLightRevealScrim == null) { + return; + } + + final boolean wakingUpFromPowerButton = wakingUp + && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal) + && mWakefulnessLifecycle.getLastWakeReason() + == PowerManager.WAKE_REASON_POWER_BUTTON; + final boolean sleepingFromPowerButton = !wakingUp + && mWakefulnessLifecycle.getLastSleepReason() + == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON; + + if (wakingUpFromPowerButton || sleepingFromPowerButton) { + mLightRevealScrim.setRevealEffect(mPowerButtonReveal); + mLightRevealScrim.setRevealAmount(1f - mStatusBarStateController.getDozeAmount()); + } else if (!wakingUp || !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { + // If we're going to sleep, but it's not from the power button, use the default reveal. + // If we're waking up, only use the default reveal if the biometric controller didn't + // already set it to the circular reveal because we're waking up from a fingerprint/face + // auth. + mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE); + mLightRevealScrim.setRevealAmount(1f - mStatusBarStateController.getDozeAmount()); + } + } + + @Override + public LightRevealScrim getLightRevealScrim() { + return mLightRevealScrim; + } + + @Override + public void onTrackingStarted() { + mShadeController.runPostCollapseRunnables(); + } + + @Override + public void onClosingFinished() { + mShadeController.runPostCollapseRunnables(); + if (!mPresenter.isPresenterFullyCollapsed()) { + // if we set it not to be focusable when collapsing, we have to undo it when we aborted + // the closing + mNotificationShadeWindowController.setNotificationShadeFocusable(true); + } + } + + @Override + public void onUnlockHintStarted() { + mFalsingCollector.onUnlockHintStarted(); + mKeyguardIndicationController.showActionToUnlock(); + } + + @Override + public void onHintFinished() { + // Delay the reset a bit so the user can read the text. + mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS); + } + + @Override + public void onCameraHintStarted() { + mFalsingCollector.onCameraHintStarted(); + mKeyguardIndicationController.showTransientIndication(R.string.camera_hint); + } + + @Override + public void onVoiceAssistHintStarted() { + mFalsingCollector.onLeftAffordanceHintStarted(); + mKeyguardIndicationController.showTransientIndication(R.string.voice_hint); + } + + @Override + public void onPhoneHintStarted() { + mFalsingCollector.onLeftAffordanceHintStarted(); + mKeyguardIndicationController.showTransientIndication(R.string.phone_hint); + } + + @Override + public void onTrackingStopped(boolean expand) { + if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { + if (!expand && !mKeyguardStateController.canDismissLockScreen()) { + mStatusBarKeyguardViewManager.showBouncer(false /* scrimmed */); + } + } + } + + // TODO: Figure out way to remove these. + @Override + public NavigationBarView getNavigationBarView() { + return mNavigationBarController.getNavigationBarView(mDisplayId); + } + + @Override + public boolean isOverviewEnabled() { + return mNavigationBarController.isOverviewEnabled(mDisplayId); + } + + @Override + public void showPinningEnterExitToast(boolean entering) { + mNavigationBarController.showPinningEnterExitToast(mDisplayId, entering); + } + + @Override + public void showPinningEscapeToast() { + mNavigationBarController.showPinningEscapeToast(mDisplayId); + } + + /** + * TODO: Remove this method. Views should not be passed forward. Will cause theme issues. + * @return bottom area view + */ + @Override + public KeyguardBottomAreaView getKeyguardBottomAreaView() { + return mNotificationPanelViewController.getKeyguardBottomAreaView(); + } + + /** + * Propagation of the bouncer state, indicating that it's fully visible. + */ + @Override + public void setBouncerShowing(boolean bouncerShowing) { + mBouncerShowing = bouncerShowing; + mKeyguardBypassController.setBouncerShowing(bouncerShowing); + mPulseExpansionHandler.setBouncerShowing(bouncerShowing); + setBouncerShowingForStatusBarComponents(bouncerShowing); + mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing); + mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */); + updateScrimController(); + if (!mBouncerShowing) { + updatePanelExpansionForKeyguard(); + } + } + + /** + * Propagate the bouncer state to status bar components. + * + * Separate from {@link #setBouncerShowing} because we sometimes re-create the status bar and + * should update only the status bar components. + */ + private void setBouncerShowingForStatusBarComponents(boolean bouncerShowing) { + int importance = bouncerShowing + ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + : IMPORTANT_FOR_ACCESSIBILITY_AUTO; + if (mPhoneStatusBarViewController != null) { + mPhoneStatusBarViewController.setImportantForAccessibility(importance); + } + mNotificationPanelViewController.setImportantForAccessibility(importance); + mNotificationPanelViewController.setBouncerShowing(bouncerShowing); + } + + /** + * Collapses the notification shade if it is tracking or expanded. + */ + @Override + public void collapseShade() { + if (mNotificationPanelViewController.isTracking()) { + mNotificationShadeWindowViewController.cancelCurrentTouch(); + } + if (mPanelExpanded && mState == StatusBarState.SHADE) { + mShadeController.animateCollapsePanels(); + } + } + + @VisibleForTesting + final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() { + @Override + public void onFinishedGoingToSleep() { + mNotificationPanelViewController.onAffordanceLaunchEnded(); + releaseGestureWakeLock(); + mLaunchCameraWhenFinishedWaking = false; + mDeviceInteractive = false; + mWakeUpComingFromTouch = false; + mWakeUpTouchLocation = null; + updateVisibleToUser(); + + updateNotificationPanelTouchState(); + mNotificationShadeWindowViewController.cancelCurrentTouch(); + if (mLaunchCameraOnFinishedGoingToSleep) { + mLaunchCameraOnFinishedGoingToSleep = false; + + // This gets executed before we will show Keyguard, so post it in order that the state + // is correct. + mMainExecutor.execute(() -> mCommandQueueCallbacks.onCameraLaunchGestureDetected( + mLastCameraLaunchSource)); + } + + if (mLaunchEmergencyActionOnFinishedGoingToSleep) { + mLaunchEmergencyActionOnFinishedGoingToSleep = false; + + // This gets executed before we will show Keyguard, so post it in order that the + // state is correct. + mMainExecutor.execute( + () -> mCommandQueueCallbacks.onEmergencyActionLaunchGestureDetected()); + } + updateIsKeyguard(); + } + + @Override + public void onStartedGoingToSleep() { + String tag = "CentralSurfaces#onStartedGoingToSleep"; + DejankUtils.startDetectingBlockingIpcs(tag); + + // cancel stale runnables that could put the device in the wrong state + cancelAfterLaunchTransitionRunnables(); + + updateRevealEffect(false /* wakingUp */); + updateNotificationPanelTouchState(); + maybeEscalateHeadsUp(); + dismissVolumeDialog(); + mWakeUpCoordinator.setFullyAwake(false); + mKeyguardBypassController.onStartedGoingToSleep(); + + // The unlocked screen off and fold to aod animations might use our LightRevealScrim - + // we need to be expanded for it to be visible. + if (mDozeParameters.shouldShowLightRevealScrim()) { + makeExpandedVisible(true); + } + + DejankUtils.stopDetectingBlockingIpcs(tag); + } + + @Override + public void onStartedWakingUp() { + String tag = "CentralSurfaces#onStartedWakingUp"; + DejankUtils.startDetectingBlockingIpcs(tag); + mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> { + mDeviceInteractive = true; + mWakeUpCoordinator.setWakingUp(true); + if (!mKeyguardBypassController.getBypassEnabled()) { + mHeadsUpManager.releaseAllImmediately(); + } + updateVisibleToUser(); + updateIsKeyguard(); + mDozeServiceHost.stopDozing(); + // This is intentionally below the stopDozing call above, since it avoids that we're + // unnecessarily animating the wakeUp transition. Animations should only be enabled + // once we fully woke up. + updateRevealEffect(true /* wakingUp */); + updateNotificationPanelTouchState(); + + // If we are waking up during the screen off animation, we should undo making the + // expanded visible (we did that so the LightRevealScrim would be visible). + if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) { + makeExpandedInvisible(); + } + + }); + DejankUtils.stopDetectingBlockingIpcs(tag); + } + + @Override + public void onFinishedWakingUp() { + mWakeUpCoordinator.setFullyAwake(true); + mWakeUpCoordinator.setWakingUp(false); + if (mLaunchCameraWhenFinishedWaking) { + mNotificationPanelViewController.launchCamera( + false /* animate */, mLastCameraLaunchSource); + mLaunchCameraWhenFinishedWaking = false; + } + if (mLaunchEmergencyActionWhenFinishedWaking) { + mLaunchEmergencyActionWhenFinishedWaking = false; + Intent emergencyIntent = getEmergencyActionIntent(); + if (emergencyIntent != null) { + mContext.startActivityAsUser(emergencyIntent, + getActivityUserHandle(emergencyIntent)); + } + } + updateScrimController(); + } + }; + + /** + * We need to disable touch events because these might + * collapse the panel after we expanded it, and thus we would end up with a blank + * Keyguard. + */ + @Override + public void updateNotificationPanelTouchState() { + boolean goingToSleepWithoutAnimation = isGoingToSleep() + && !mDozeParameters.shouldControlScreenOff(); + boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing()) + || goingToSleepWithoutAnimation; + mNotificationPanelViewController.setTouchAndAnimationDisabled(disabled); + mNotificationIconAreaController.setAnimationsEnabled(!disabled); + } + + final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { + @Override + public void onScreenTurningOn(Runnable onDrawn) { + mFalsingCollector.onScreenTurningOn(); + mNotificationPanelViewController.onScreenTurningOn(); + } + + @Override + public void onScreenTurnedOn() { + mScrimController.onScreenTurnedOn(); + } + + @Override + public void onScreenTurnedOff() { + Trace.beginSection("CentralSurfaces#onScreenTurnedOff"); + mFalsingCollector.onScreenOff(); + mScrimController.onScreenTurnedOff(); + if (mCloseQsBeforeScreenOff) { + mNotificationPanelViewController.closeQs(); + mCloseQsBeforeScreenOff = false; + } + updateIsKeyguard(); + Trace.endSection(); + } + }; + + @Override + public int getWakefulnessState() { + return mWakefulnessLifecycle.getWakefulness(); + } + + /** + * @return true if the screen is currently fully off, i.e. has finished turning off and has + * since not started turning on. + */ + @Override + public boolean isScreenFullyOff() { + return mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_OFF; + } + + @Override + public void showScreenPinningRequest(int taskId, boolean allowCancel) { + mScreenPinningRequest.showPrompt(taskId, allowCancel); + } + + @Nullable + @Override + public Intent getEmergencyActionIntent() { + Intent emergencyIntent = new Intent(EmergencyGesture.ACTION_LAUNCH_EMERGENCY); + PackageManager pm = mContext.getPackageManager(); + List<ResolveInfo> emergencyActivities = pm.queryIntentActivities(emergencyIntent, + PackageManager.MATCH_SYSTEM_ONLY); + ResolveInfo resolveInfo = getTopEmergencySosInfo(emergencyActivities); + if (resolveInfo == null) { + Log.wtf(TAG, "Couldn't find an app to process the emergency intent."); + return null; + } + emergencyIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName, + resolveInfo.activityInfo.name)); + emergencyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return emergencyIntent; + } + + /** + * Select and return the "best" ResolveInfo for Emergency SOS Activity. + */ + private @Nullable ResolveInfo getTopEmergencySosInfo(List<ResolveInfo> emergencyActivities) { + // No matched activity. + if (emergencyActivities == null || emergencyActivities.isEmpty()) { + return null; + } + + // Of multiple matched Activities, give preference to the pre-set package name. + String preferredAppPackageName = + mContext.getString(R.string.config_preferredEmergencySosPackage); + + // If there is no preferred app, then return first match. + if (TextUtils.isEmpty(preferredAppPackageName)) { + return emergencyActivities.get(0); + } + + for (ResolveInfo emergencyInfo: emergencyActivities) { + // If activity is from the preferred app, use it. + if (TextUtils.equals(emergencyInfo.activityInfo.packageName, preferredAppPackageName)) { + return emergencyInfo; + } + } + // No matching activity: return first match + return emergencyActivities.get(0); + } + + @Override + public boolean isCameraAllowedByAdmin() { + if (mDevicePolicyManager.getCameraDisabled(null, + mLockscreenUserManager.getCurrentUserId())) { + return false; + } else if (mStatusBarKeyguardViewManager == null + || (isKeyguardShowing() && isKeyguardSecure())) { + // Check if the admin has disabled the camera specifically for the keyguard + return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, + mLockscreenUserManager.getCurrentUserId()) + & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0; + } + return true; + } + + @Override + public boolean isGoingToSleep() { + return mWakefulnessLifecycle.getWakefulness() + == WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP; + } + + boolean isWakingOrAwake() { + return mWakefulnessLifecycle.getWakefulness() == WakefulnessLifecycle.WAKEFULNESS_WAKING + || mWakefulnessLifecycle.getWakefulness() == WakefulnessLifecycle.WAKEFULNESS_AWAKE; + } + + @Override + public void notifyBiometricAuthModeChanged() { + mDozeServiceHost.updateDozing(); + updateScrimController(); + } + + /** + * Set the amount of progress we are currently in if we're transitioning to the full shade. + * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full + * shade. + */ + @Override + public void setTransitionToFullShadeProgress(float transitionToFullShadeProgress) { + mTransitionToFullShadeProgress = transitionToFullShadeProgress; + } + + /** + * Sets the amount of progress to the bouncer being fully hidden/visible. 1 means the bouncer + * is fully hidden, while 0 means the bouncer is visible. + */ + @Override + public void setBouncerHiddenFraction(float expansion) { + mScrimController.setBouncerHiddenFraction(expansion); + } + + @Override + @VisibleForTesting + public void updateScrimController() { + Trace.beginSection("CentralSurfaces#updateScrimController"); + + boolean unlocking = mKeyguardStateController.isShowing() && ( + mBiometricUnlockController.isWakeAndUnlock() + || mKeyguardStateController.isKeyguardFadingAway() + || mKeyguardStateController.isKeyguardGoingAway() + || mKeyguardViewMediator.requestedShowSurfaceBehindKeyguard() + || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()); + + mScrimController.setExpansionAffectsAlpha(!unlocking); + + boolean launchingAffordanceWithPreview = + mNotificationPanelViewController.isLaunchingAffordanceWithPreview(); + mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview); + + if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { + if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED + || mTransitionToFullShadeProgress > 0f) { + mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE); + } else { + mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED); + } + } else if (mBouncerShowing && !unlocking) { + // Bouncer needs the front scrim when it's on top of an activity, + // tapping on a notification, editing QS or being dismissed by + // FLAG_DISMISS_KEYGUARD_ACTIVITY. + ScrimState state = mStatusBarKeyguardViewManager.bouncerNeedsScrimming() + ? ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER; + mScrimController.transitionTo(state); + } else if (launchingAffordanceWithPreview) { + // We want to avoid animating when launching with a preview. + mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); + } else if (mBrightnessMirrorVisible) { + mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR); + } else if (mState == StatusBarState.SHADE_LOCKED) { + mScrimController.transitionTo(ScrimState.SHADE_LOCKED); + } else if (mDozeServiceHost.isPulsing()) { + mScrimController.transitionTo(ScrimState.PULSING, + mDozeScrimController.getScrimCallback()); + } else if (mDozeServiceHost.hasPendingScreenOffCallback()) { + mScrimController.transitionTo(ScrimState.OFF, new ScrimController.Callback() { + @Override + public void onFinished() { + mDozeServiceHost.executePendingScreenOffCallback(); + } + }); + } else if (mDozing && !unlocking) { + mScrimController.transitionTo(ScrimState.AOD); + } else if (mKeyguardStateController.isShowing() && !isOccluded() && !unlocking) { + mScrimController.transitionTo(ScrimState.KEYGUARD); + } else if (mKeyguardStateController.isShowing() && mKeyguardUpdateMonitor.isDreaming()) { + mScrimController.transitionTo(ScrimState.DREAMING); + } else { + mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); + } + updateLightRevealScrimVisibility(); + + Trace.endSection(); + } + + @Override + public boolean isKeyguardShowing() { + if (mStatusBarKeyguardViewManager == null) { + Slog.i(TAG, "isKeyguardShowing() called before startKeyguard(), returning true"); + return true; + } + return mStatusBarKeyguardViewManager.isShowing(); + } + + @Override + public boolean shouldIgnoreTouch() { + return (mStatusBarStateController.isDozing() + && mDozeServiceHost.getIgnoreTouchWhilePulsing()) + || mScreenOffAnimationController.shouldIgnoreKeyguardTouches(); + } + + // Begin Extra BaseStatusBar methods. + + protected final CommandQueue mCommandQueue; + protected IStatusBarService mBarService; + + // all notifications + protected NotificationStackScrollLayout mStackScroller; + + // handling reordering + private final VisualStabilityManager mVisualStabilityManager; + + protected AccessibilityManager mAccessibilityManager; + + protected boolean mDeviceInteractive; + + protected boolean mVisible; + + // mScreenOnFromKeyguard && mVisible. + private boolean mVisibleToUser; + + protected DevicePolicyManager mDevicePolicyManager; + private final PowerManager mPowerManager; + protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + + protected KeyguardManager mKeyguardManager; + private final DeviceProvisionedController mDeviceProvisionedController; + + private final NavigationBarController mNavigationBarController; + private final AccessibilityFloatingMenuController mAccessibilityFloatingMenuController; + + // UI-specific methods + + protected WindowManager mWindowManager; + protected IWindowManager mWindowManagerService; + private IDreamManager mDreamManager; + + protected Display mDisplay; + private int mDisplayId; + + protected NotificationShelfController mNotificationShelfController; + + private final Lazy<AssistManager> mAssistManagerLazy; + + @Override + public boolean isDeviceInteractive() { + return mDeviceInteractive; + } + + private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) { + NotificationManager noMan = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + noMan.cancel(com.android.internal.messages.nano.SystemMessageProto.SystemMessage. + NOTE_HIDDEN_NOTIFICATIONS); + + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); + if (BANNER_ACTION_SETUP.equals(action)) { + mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, + true /* force */); + mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + + ); + } + } + } + }; + + @Override + public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) { + mNotificationsController.setNotificationSnoozed(sbn, snoozeOption); + } + + + @Override + public void awakenDreams() { + mUiBgExecutor.execute(() -> { + try { + mDreamManager.awaken(); + } catch (RemoteException e) { + e.printStackTrace(); + } + }); + } + + protected void toggleKeyboardShortcuts(int deviceId) { + KeyboardShortcuts.toggle(mContext, deviceId); + } + + protected void dismissKeyboardShortcuts() { + KeyboardShortcuts.dismiss(); + } + + /** + * Dismiss the keyguard then execute an action. + * + * @param action The action to execute after dismissing the keyguard. + * @param collapsePanel Whether we should collapse the panel after dismissing the keyguard. + * @param willAnimateOnKeyguard Whether {@param action} will run an animation on the keyguard if + * we are locked. + */ + private void executeActionDismissingKeyguard(Runnable action, boolean afterKeyguardGone, + boolean collapsePanel, boolean willAnimateOnKeyguard) { + if (!mDeviceProvisionedController.isDeviceProvisioned()) return; + + OnDismissAction onDismissAction = new OnDismissAction() { + @Override + public boolean onDismiss() { + new Thread(() -> { + try { + // The intent we are sending is for the application, which + // won't have permission to immediately start an activity after + // the user switches to home. We know it is safe to do at this + // point, so make sure new activity switches are now allowed. + ActivityManager.getService().resumeAppSwitches(); + } catch (RemoteException e) { + } + action.run(); + }).start(); + + return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard; + } + + @Override + public boolean willRunAnimationOnKeyguard() { + return willAnimateOnKeyguard; + } + }; + dismissKeyguardThenExecute(onDismissAction, afterKeyguardGone); + } + + @Override + public void startPendingIntentDismissingKeyguard(final PendingIntent intent) { + startPendingIntentDismissingKeyguard(intent, null); + } + + @Override + public void startPendingIntentDismissingKeyguard( + final PendingIntent intent, @Nullable final Runnable intentSentUiThreadCallback) { + startPendingIntentDismissingKeyguard(intent, intentSentUiThreadCallback, + (ActivityLaunchAnimator.Controller) null); + } + + @Override + public void startPendingIntentDismissingKeyguard(PendingIntent intent, + Runnable intentSentUiThreadCallback, View associatedView) { + ActivityLaunchAnimator.Controller animationController = null; + if (associatedView instanceof ExpandableNotificationRow) { + animationController = mNotificationAnimationProvider.getAnimatorController( + ((ExpandableNotificationRow) associatedView)); + } + + startPendingIntentDismissingKeyguard(intent, intentSentUiThreadCallback, + animationController); + } + + @Override + public void startPendingIntentDismissingKeyguard( + final PendingIntent intent, @Nullable final Runnable intentSentUiThreadCallback, + @Nullable ActivityLaunchAnimator.Controller animationController) { + final boolean willLaunchResolverActivity = intent.isActivity() + && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(), + mLockscreenUserManager.getCurrentUserId()); + + boolean animate = !willLaunchResolverActivity + && animationController != null + && shouldAnimateLaunch(intent.isActivity()); + + // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we run + // the animation on the keyguard). The animation will take care of (instantly) collapsing + // the shade and hiding the keyguard once it is done. + boolean collapse = !animate; + executeActionDismissingKeyguard(() -> { + try { + // We wrap animationCallback with a StatusBarLaunchAnimatorController so that the + // shade is collapsed after the animation (or when it is cancelled, aborted, etc). + ActivityLaunchAnimator.Controller controller = + animationController != null ? new StatusBarLaunchAnimatorController( + animationController, this, intent.isActivity()) : null; + + mActivityLaunchAnimator.startPendingIntentWithAnimation( + controller, animate, intent.getCreatorPackage(), + (animationAdapter) -> { + ActivityOptions options = new ActivityOptions( + CentralSurfaces.getActivityOptions( + mDisplayId, animationAdapter)); + // TODO b/221255671: restrict this to only be set for notifications + options.setEligibleForLegacyPermissionPrompt(true); + return intent.sendAndReturnResult(null, 0, null, null, null, + null, options.toBundle()); + }); + } catch (PendingIntent.CanceledException e) { + // the stack trace isn't very helpful here. + // Just log the exception message. + Log.w(TAG, "Sending intent failed: " + e); + if (!collapse) { + // executeActionDismissingKeyguard did not collapse for us already. + collapsePanelOnMainThread(); + } + // TODO: Dismiss Keyguard. + } + if (intent.isActivity()) { + mAssistManagerLazy.get().hideAssist(); + } + if (intentSentUiThreadCallback != null) { + postOnUiThread(intentSentUiThreadCallback); + } + }, willLaunchResolverActivity, collapse, animate); + } + + private void postOnUiThread(Runnable runnable) { + mMainExecutor.execute(runnable); + } + + @Override + public void visibilityChanged(boolean visible) { + if (mVisible != visible) { + mVisible = visible; + if (!visible) { + mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, + true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */); + } + } + updateVisibleToUser(); + } + + protected void updateVisibleToUser() { + boolean oldVisibleToUser = mVisibleToUser; + mVisibleToUser = mVisible && mDeviceInteractive; + + if (oldVisibleToUser != mVisibleToUser) { + handleVisibleToUserChanged(mVisibleToUser); + } + } + + /** + * Clear Buzz/Beep/Blink. + */ + @Override + public void clearNotificationEffects() { + try { + mBarService.clearNotificationEffects(); + } catch (RemoteException e) { + // Won't fail unless the world has ended. + } + } + + /** + * @return Whether the security bouncer from Keyguard is showing. + */ + @Override + public boolean isBouncerShowing() { + return mBouncerShowing; + } + + /** + * @return Whether the security bouncer from Keyguard is showing. + */ + @Override + public boolean isBouncerShowingScrimmed() { + return isBouncerShowing() && mStatusBarKeyguardViewManager.bouncerNeedsScrimming(); + } + + @Override + public boolean isBouncerShowingOverDream() { + return isBouncerShowing() && mDreamOverlayStateController.isOverlayActive(); + } + + /** + * When {@link KeyguardBouncer} starts to be dismissed, playing its animation. + */ + @Override + public void onBouncerPreHideAnimation() { + mNotificationPanelViewController.onBouncerPreHideAnimation(); + + } + + @Override + public boolean isKeyguardSecure() { + if (mStatusBarKeyguardViewManager == null) { + // startKeyguard() hasn't been called yet, so we don't know. + // Make sure anything that needs to know isKeyguardSecure() checks and re-checks this + // value onVisibilityChanged(). + Slog.w(TAG, "isKeyguardSecure() called before startKeyguard(), returning false", + new Throwable()); + return false; + } + return mStatusBarKeyguardViewManager.isSecure(); + } + @Override + public NotificationPanelViewController getPanelController() { + return mNotificationPanelViewController; + } + // End Extra BaseStatusBarMethods. + + @Override + public NotificationGutsManager getGutsManager() { + return mGutsManager; + } + + boolean isTransientShown() { + return mTransientShown; + } + + private void updateLightRevealScrimVisibility() { + if (mLightRevealScrim == null) { + // status bar may not be inflated yet + return; + } + + mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha()); + } + + @Override + public void extendDozePulse(){ + mDozeScrimController.extendPulse(); + } + + private final KeyguardUpdateMonitorCallback mUpdateCallback = + new KeyguardUpdateMonitorCallback() { + @Override + public void onDreamingStateChanged(boolean dreaming) { + updateScrimController(); + if (dreaming) { + maybeEscalateHeadsUp(); + } + } + + // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by + // KeyguardCoordinator + @Override + public void onStrongAuthStateChanged(int userId) { + super.onStrongAuthStateChanged(userId); + mNotificationsController.requestNotificationUpdate("onStrongAuthStateChanged"); + } + }; + + + private final FalsingManager.FalsingBeliefListener mFalsingBeliefListener = + new FalsingManager.FalsingBeliefListener() { + @Override + public void onFalse() { + // Hides quick settings, bouncer, and quick-quick settings. + mStatusBarKeyguardViewManager.reset(true); + } + }; + + // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over, + // this animation is tied to the scrim for historic reasons. + // TODO: notify when keyguard has faded away instead of the scrim. + private final ScrimController.Callback mUnlockScrimCallback = new ScrimController + .Callback() { + @Override + public void onFinished() { + if (mStatusBarKeyguardViewManager == null) { + Log.w(TAG, "Tried to notify keyguard visibility when " + + "mStatusBarKeyguardViewManager was null"); + return; + } + if (mKeyguardStateController.isKeyguardFadingAway()) { + mStatusBarKeyguardViewManager.onKeyguardFadedAway(); + } + } + + @Override + public void onCancelled() { + onFinished(); + } + }; + + private final DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() { + @Override + public void onUserSetupChanged() { + final boolean userSetup = mDeviceProvisionedController.isCurrentUserSetup(); + Log.d(TAG, "mUserSetupObserver - DeviceProvisionedListener called for " + + "current user"); + if (MULTIUSER_DEBUG) { + Log.d(TAG, String.format("User setup changed: userSetup=%s mUserSetup=%s", + userSetup, mUserSetup)); + } + + if (userSetup != mUserSetup) { + mUserSetup = userSetup; + if (!mUserSetup) { + animateCollapseQuickSettings(); + } + if (mNotificationPanelViewController != null) { + mNotificationPanelViewController.setUserSetupComplete(mUserSetup); + } + updateQsExpansionEnabled(); + } + } + }; + + private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (!mWallpaperSupported) { + // Receiver should not have been registered at all... + Log.wtf(TAG, "WallpaperManager not supported"); + return; + } + WallpaperInfo info = mWallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT); + mWallpaperController.onWallpaperInfoUpdated(info); + + final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_dozeSupportsAodWallpaper); + // If WallpaperInfo is null, it must be ImageWallpaper. + final boolean supportsAmbientMode = deviceSupportsAodWallpaper + && (info != null && info.supportsAmbientMode()); + + mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode); + mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode); + mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode); + } + }; + + private final ConfigurationListener mConfigurationListener = new ConfigurationListener() { + @Override + public void onConfigChanged(Configuration newConfig) { + updateResources(); + updateDisplaySize(); // populates mDisplayMetrics + + if (DEBUG) { + Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration()); + } + + if (!mNotifPipelineFlags.isNewPipelineEnabled()) { + mViewHierarchyManager.updateRowStates(); + } + mScreenPinningRequest.onConfigurationChanged(); + } + + @Override + public void onDensityOrFontScaleChanged() { + // TODO: Remove this. + if (mBrightnessMirrorController != null) { + mBrightnessMirrorController.onDensityOrFontScaleChanged(); + } + // TODO: Bring these out of CentralSurfaces. + mUserInfoControllerImpl.onDensityOrFontScaleChanged(); + mUserSwitcherController.onDensityOrFontScaleChanged(); + mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext); + mHeadsUpManager.onDensityOrFontScaleChanged(); + } + + @Override + public void onThemeChanged() { + if (mBrightnessMirrorController != null) { + mBrightnessMirrorController.onOverlayChanged(); + } + // We need the new R.id.keyguard_indication_area before recreating + // mKeyguardIndicationController + mNotificationPanelViewController.onThemeChanged(); + + if (mStatusBarKeyguardViewManager != null) { + mStatusBarKeyguardViewManager.onThemeChanged(); + } + if (mAmbientIndicationContainer instanceof AutoReinflateContainer) { + ((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout(); + } + mNotificationIconAreaController.onThemeChanged(); + } + + @Override + public void onUiModeChanged() { + if (mBrightnessMirrorController != null) { + mBrightnessMirrorController.onUiModeChanged(); + } + } + }; + + private StatusBarStateController.StateListener mStateListener = + new StatusBarStateController.StateListener() { + @Override + public void onStatePreChange(int oldState, int newState) { + // If we're visible and switched to SHADE_LOCKED (the user dragged + // down on the lockscreen), clear notification LED, vibration, + // ringing. + // Other transitions are covered in handleVisibleToUserChanged(). + if (mVisible && (newState == StatusBarState.SHADE_LOCKED + || mStatusBarStateController.goingToFullShade())) { + clearNotificationEffects(); + } + if (newState == StatusBarState.KEYGUARD) { + mRemoteInputManager.onPanelCollapsed(); + maybeEscalateHeadsUp(); + } + } + + @Override + public void onStateChanged(int newState) { + mState = newState; + updateReportRejectedTouchVisibility(); + mDozeServiceHost.updateDozing(); + updateTheme(); + mNavigationBarController.touchAutoDim(mDisplayId); + Trace.beginSection("CentralSurfaces#updateKeyguardState"); + if (mState == StatusBarState.KEYGUARD) { + mNotificationPanelViewController.cancelPendingPanelCollapse(); + } + updateDozingState(); + checkBarModes(); + updateScrimController(); + mPresenter.updateMediaMetaData(false, mState != StatusBarState.KEYGUARD); + Trace.endSection(); + } + + @Override + public void onDozeAmountChanged(float linear, float eased) { + if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS) + && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { + mLightRevealScrim.setRevealAmount(1f - linear); + } + } + + @Override + public void onDozingChanged(boolean isDozing) { + Trace.beginSection("CentralSurfaces#updateDozing"); + mDozing = isDozing; + + // Collapse the notification panel if open + boolean dozingAnimated = mDozeServiceHost.getDozingRequested() + && mDozeParameters.shouldControlScreenOff(); + mNotificationPanelViewController.resetViews(dozingAnimated); + + updateQsExpansionEnabled(); + mKeyguardViewMediator.setDozing(mDozing); + + mNotificationsController.requestNotificationUpdate("onDozingChanged"); + updateDozingState(); + mDozeServiceHost.updateDozing(); + updateScrimController(); + updateReportRejectedTouchVisibility(); + Trace.endSection(); + } + + @Override + public void onFullscreenStateChanged(boolean isFullscreen) { + mIsFullscreen = isFullscreen; + maybeUpdateBarMode(); + } + }; + + private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback = + new BatteryController.BatteryStateChangeCallback() { + @Override + public void onPowerSaveChanged(boolean isPowerSave) { + mMainExecutor.execute(mCheckBarModes); + if (mDozeServiceHost != null) { + mDozeServiceHost.firePowerSaveChanged(isPowerSave); + } + } + }; + + private final ActivityLaunchAnimator.Callback mActivityLaunchAnimatorCallback = + new ActivityLaunchAnimator.Callback() { + @Override + public boolean isOnKeyguard() { + return mKeyguardStateController.isShowing(); + } + + @Override + public void hideKeyguardWithAnimation(IRemoteAnimationRunner runner) { + // We post to the main thread for 2 reasons: + // 1. KeyguardViewMediator is not thread-safe. + // 2. To ensure that ViewMediatorCallback#keyguardDonePending is called before + // ViewMediatorCallback#readyForKeyguardDone. The wrong order could occur + // when doing + // dismissKeyguardThenExecute { hideKeyguardWithAnimation(runner) }. + mMainExecutor.execute(() -> mKeyguardViewMediator.hideWithAnimation(runner)); + } + + @Override + public int getBackgroundColor(TaskInfo task) { + if (!mStartingSurfaceOptional.isPresent()) { + Log.w(TAG, "No starting surface, defaulting to SystemBGColor"); + return SplashscreenContentDrawer.getSystemBGColor(); + } + + return mStartingSurfaceOptional.get().getBackgroundColor(task); + } + }; + + private final ActivityLaunchAnimator.Listener mActivityLaunchAnimatorListener = + new ActivityLaunchAnimator.Listener() { + @Override + public void onLaunchAnimationStart() { + mKeyguardViewMediator.setBlursDisabledForAppLaunch(true); + } + + @Override + public void onLaunchAnimationEnd() { + mKeyguardViewMediator.setBlursDisabledForAppLaunch(false); + } + }; + + private final DemoMode mDemoModeCallback = new DemoMode() { + @Override + public void onDemoModeFinished() { + checkBarModes(); + } + + @Override + public void dispatchDemoCommand(String command, Bundle args) { } + }; + + /** + * Determines what UserHandle to use when launching an activity. + * + * We want to ensure that activities that are launched within the systemui process should be + * launched as user of the current process. + * @param intent + * @return UserHandle + */ + private UserHandle getActivityUserHandle(Intent intent) { + String[] packages = mContext.getResources().getStringArray(R.array.system_ui_packages); + for (String pkg : packages) { + if (intent.getComponent() == null) break; + if (pkg.equals(intent.getComponent().getPackageName())) { + return new UserHandle(UserHandle.myUserId()); + } + } + return UserHandle.CURRENT; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 034b751d1e61..2dc3261eb886 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -27,11 +27,13 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Icon; import android.util.AttributeSet; +import android.util.MathUtils; import android.util.Property; import android.view.ContextThemeWrapper; import android.view.View; import android.view.animation.Interpolator; +import androidx.annotation.VisibleForTesting; import androidx.collection.ArrayMap; import com.android.internal.statusbar.StatusBarIcon; @@ -136,6 +138,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { }.setDuration(CONTENT_FADE_DURATION); private static final int MAX_ICONS_ON_AOD = 3; + + /* Maximum number of icons in short shelf on lockscreen when also showing overflow dot. */ public static final int MAX_ICONS_ON_LOCKSCREEN = 3; public static final int MAX_STATIC_ICONS = 4; private static final int MAX_DOTS = 1; @@ -145,7 +149,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { private int mDotPadding; private int mStaticDotRadius; private int mStaticDotDiameter; - private int mOverflowWidth; private int mActualLayoutWidth = NO_VALUE; private float mActualPaddingEnd = NO_VALUE; private float mActualPaddingStart = NO_VALUE; @@ -219,10 +222,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { paint.setColor(Color.RED); canvas.drawLine(mVisualOverflowStart, 0, mVisualOverflowStart, height, paint); - - paint.setColor(Color.YELLOW); - float overflow = getMaxOverflowStart(); - canvas.drawLine(overflow, 0, overflow, height, paint); } } @@ -255,14 +254,14 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } } - private void setIconSize(int size) { + @VisibleForTesting + public void setIconSize(int size) { mIconSize = size; - mOverflowWidth = mIconSize + (MAX_DOTS - 1) * (mStaticDotDiameter + mDotPadding); } private void updateState() { resetViewStates(); - calculateIconTranslations(); + calculateIconXTranslations(); applyIconStates(); } @@ -390,12 +389,11 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { * @return Width of shelf for the given number of icons */ public float calculateWidthFor(float numIcons) { - if (getChildCount() == 0) { + if (numIcons == 0) { return 0f; } - final float contentWidth = numIcons <= MAX_ICONS_ON_LOCKSCREEN + 1 - ? numIcons * mIconSize - : MAX_ICONS_ON_LOCKSCREEN * mIconSize + (float) mOverflowWidth; + final float contentWidth = + mIconSize * MathUtils.min(numIcons, MAX_ICONS_ON_LOCKSCREEN + 1); return getActualPaddingStart() + contentWidth + getActualPaddingEnd(); @@ -406,14 +404,13 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { * are inserted into the notification container. * If this is not a whole number, the fraction means by how much the icon is appearing. */ - public void calculateIconTranslations() { + public void calculateIconXTranslations() { float translationX = getActualPaddingStart(); int firstOverflowIndex = -1; int childCount = getChildCount(); int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD : mIsStaticLayout ? MAX_STATIC_ICONS : childCount; float layoutEnd = getLayoutEnd(); - float overflowStart = getMaxOverflowStart(); mVisualOverflowStart = 0; mFirstVisibleIconState = null; for (int i = 0; i < childCount; i++) { @@ -438,12 +435,12 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { ? StatusBarIconView.STATE_HIDDEN : StatusBarIconView.STATE_ICON; - boolean isOverflowing = - (translationX > (isLastChild ? layoutEnd - mIconSize - : overflowStart - mIconSize)); + final float overflowDotX = layoutEnd - mIconSize; + boolean isOverflowing = translationX > overflowDotX; + if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) { firstOverflowIndex = isLastChild && !forceOverflow ? i - 1 : i; - mVisualOverflowStart = layoutEnd - mOverflowWidth; + mVisualOverflowStart = layoutEnd - mIconSize; if (forceOverflow || mIsStaticLayout) { mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart); } @@ -477,7 +474,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mLastVisibleIconState = mIconStates.get(lastChild); mFirstVisibleIconState = mIconStates.get(getChildAt(0)); } - if (isLayoutRtl()) { for (int i = 0; i < childCount; i++) { View view = getChildAt(i); @@ -568,7 +564,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } private float getMaxOverflowStart() { - return getLayoutEnd() - mOverflowWidth; + return getLayoutEnd() - mIconSize; } public void setChangingViewPositions(boolean changingViewPositions) { @@ -635,7 +631,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { return 0; } - int collapsedPadding = mOverflowWidth; + int collapsedPadding = mIconSize; if (collapsedPadding + getFinalTranslationX() > getWidth()) { collapsedPadding = getWidth() - getFinalTranslationX(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index e4f42b10dab0..98a711d122fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -179,6 +179,7 @@ import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.phone.panelstate.PanelState; +import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -310,6 +311,7 @@ public class NotificationPanelViewController extends PanelViewController { private final NotificationRemoteInputManager mRemoteInputManager; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; + private final ShadeTransitionController mShadeTransitionController; private final TapAgainViewController mTapAgainViewController; private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController; private final RecordingController mRecordingController; @@ -389,6 +391,12 @@ public class NotificationPanelViewController extends PanelViewController { private int mLargeScreenShadeHeaderHeight; private int mSplitShadeNotificationsScrimMarginBottom; + /** + * Vertical overlap allowed between the bottom of the notification shelf and + * the top of the lock icon or the under-display fingerprint sensor background. + */ + private int mShelfAndLockIconOverlap; + private final KeyguardClockPositionAlgorithm mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(); @@ -745,7 +753,8 @@ public class NotificationPanelViewController extends PanelViewController { NotificationListContainer notificationListContainer, PanelEventsEmitter panelEventsEmitter, NotificationStackSizeCalculator notificationStackSizeCalculator, - UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, + ShadeTransitionController shadeTransitionController) { super(view, falsingManager, dozeLog, @@ -826,7 +835,9 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardBypassController = bypassController; mUpdateMonitor = keyguardUpdateMonitor; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; + mShadeTransitionController = shadeTransitionController; lockscreenShadeTransitionController.setNotificationPanelController(this); + shadeTransitionController.setNotificationPanelViewController(this); DynamicPrivacyControlListener dynamicPrivacyControlListener = new DynamicPrivacyControlListener(); @@ -885,7 +896,10 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void onUnlockAnimationStarted( - boolean playingCannedAnimation, boolean isWakeAndUnlock) { + boolean playingCannedAnimation, + boolean isWakeAndUnlock, + long unlockAnimationStartDelay, + long unlockAnimationDuration) { // Disable blurs while we're unlocking so that panel expansion does not // cause blurring. This will eventually be re-enabled by the panel view on // ACTION_UP, since the user's finger might still be down after a swipe to @@ -902,7 +916,22 @@ public class NotificationPanelViewController extends PanelViewController { onTrackingStopped(false); instantCollapse(); } else { - fling(0f, false, 1f, false); + mView.animate() + .alpha(0f) + .setStartDelay(0) + // Translate up by 4%. + .translationY(mView.getHeight() * -0.04f) + // This start delay is to give us time to animate out before + // the launcher icons animation starts, so use that as our + // duration. + .setDuration(unlockAnimationStartDelay) + .setInterpolator(EMPHASIZED_DECELERATE) + .withEndAction(() -> { + instantCollapse(); + mView.setAlpha(1f); + mView.setTranslationY(0f); + }) + .start(); } } } @@ -1081,6 +1110,9 @@ public class NotificationPanelViewController extends PanelViewController { mResources.getDimensionPixelSize( R.dimen.split_shade_notifications_scrim_margin_bottom); + mShelfAndLockIconOverlap = + mResources.getDimensionPixelSize(R.dimen.shelf_and_lock_icon_overlap); + final boolean newShouldUseSplitNotificationShade = LargeScreenUtils.shouldUseSplitNotificationShade(mResources); final boolean splitNotificationShadeChanged = @@ -1466,27 +1498,18 @@ public class NotificationPanelViewController extends PanelViewController { } /** - * @return the maximum keyguard notifications that can fit on the screen + * @return Space available to show notifications on lockscreen. */ @VisibleForTesting - int computeMaxKeyguardNotifications() { - if (mAmbientState.getFractionToShade() > 0 || mAmbientState.getDozeAmount() > 0) { - return mMaxAllowedKeyguardNotifications; - } + float getSpaceForLockscreenNotifications() { float topPadding = mNotificationStackScrollLayoutController.getTopPadding(); - float shelfIntrinsicHeight = - mNotificationShelfController.getVisibility() == View.GONE - ? 0 - : mNotificationShelfController.getIntrinsicHeight(); - // Padding to add to the bottom of the stack to keep a minimum distance from the top of - // the lock icon. - float lockIconPadding = 0; + // Space between bottom of notifications and top of lock icon or udfps background. + float lockIconPadding = mLockIconViewController.getTop(); if (mLockIconViewController.getTop() != 0) { - final float lockIconTopWithPadding = mLockIconViewController.getTop() - - mResources.getDimensionPixelSize(R.dimen.min_lock_icon_padding); lockIconPadding = mNotificationStackScrollLayoutController.getBottom() - - lockIconTopWithPadding; + - mLockIconViewController.getTop() + - mShelfAndLockIconOverlap; } float bottomPadding = Math.max(lockIconPadding, @@ -1497,9 +1520,26 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScrollLayoutController.getHeight() - topPadding - bottomPadding; + return availableSpace; + } + + /** + * @return Maximum number of notifications that can fit on keyguard. + */ + @VisibleForTesting + int computeMaxKeyguardNotifications() { + if (mAmbientState.getFractionToShade() > 0 || mAmbientState.getDozeAmount() > 0) { + return mMaxAllowedKeyguardNotifications; + } + + final float shelfIntrinsicHeight = + mNotificationShelfController.getVisibility() == View.GONE + ? 0 + : mNotificationShelfController.getIntrinsicHeight(); return mNotificationStackSizeCalculator.computeMaxKeyguardNotifications( - mNotificationStackScrollLayoutController.getView(), availableSpace, + mNotificationStackScrollLayoutController.getView(), + getSpaceForLockscreenNotifications(), shelfIntrinsicHeight); } @@ -3607,6 +3647,7 @@ public class NotificationPanelViewController extends PanelViewController { } }); mLockscreenShadeTransitionController.setQS(mQs); + mShadeTransitionController.setQs(mQs); mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader()); mQs.setScrollListener(mScrollListener); updateQsExpansion(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index 2f11b16f9383..be5b33eb0da0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -252,7 +252,7 @@ public class NotificationShadeWindowViewController { } if (mStatusBarStateController.isDozing()) { - mService.mDozeScrimController.extendPulse(); + mService.extendDozePulse(); } mLockIconViewController.onTouchEvent( ev, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 9e707644782c..6637394e2b2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -108,6 +108,8 @@ public abstract class PanelViewController { */ private boolean mIsSpringBackAnimation; + private boolean mInSplitShade; + private void logf(String fmt, Object... args) { Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); } @@ -303,8 +305,9 @@ public abstract class PanelViewController { mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); mHintDistance = mResources.getDimension(R.dimen.hint_move_distance); mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount); - mUnlockFalsingThreshold = mResources.getDimensionPixelSize( - R.dimen.unlock_falsing_threshold); + mUnlockFalsingThreshold = + mResources.getDimensionPixelSize(R.dimen.unlock_falsing_threshold); + mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade); } protected float getTouchSlop(MotionEvent event) { @@ -600,10 +603,12 @@ public abstract class PanelViewController { } mIsFlinging = true; // we want to perform an overshoot animation when flinging open - final boolean addOverscroll = expand - && mStatusBarStateController.getState() != StatusBarState.KEYGUARD - && mOverExpansion == 0.0f - && vel >= 0; + final boolean addOverscroll = + expand + && !mInSplitShade // Split shade has its own overscroll logic + && mStatusBarStateController.getState() != StatusBarState.KEYGUARD + && mOverExpansion == 0.0f + && vel >= 0; final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand); float overshootAmount = 0.0f; if (addOverscroll) { @@ -777,7 +782,8 @@ public abstract class PanelViewController { } float maxPanelHeight = getMaxPanelHeight(); if (mHeightAnimator == null) { - if (mTracking) { + // Split shade has its own overscroll logic + if (mTracking && !mInSplitShade) { float overExpansionPixels = Math.max(0, h - maxPanelHeight); setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java index 5d38eea15723..c5e5297ae6ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java @@ -31,6 +31,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutListContainerModule; import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks; +import com.android.systemui.statusbar.phone.CentralSurfacesImpl; import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; @@ -51,7 +52,7 @@ import dagger.Subcomponent; /** * Dagger subcomponent for classes (semi-)related to the status bar. The component is created once - * inside {@link com.android.systemui.statusbar.phone.CentralSurfaces} and never re-created. + * inside {@link CentralSurfacesImpl} and never re-created. * * TODO(b/197137564): This should likely be re-factored a bit. It includes classes that aren't * directly related to status bar functionality, like multiple notification classes. And, the fact diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index c024c7245c45..942d186e7005 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -16,317 +16,22 @@ package com.android.systemui.statusbar.phone.dagger; -import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; - -import android.app.WallpaperManager; -import android.content.Context; -import android.hardware.devicestate.DeviceStateManager; -import android.os.Handler; -import android.os.PowerManager; -import android.util.DisplayMetrics; - -import com.android.internal.jank.InteractionJankMonitor; -import com.android.internal.logging.MetricsLogger; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.ViewMediatorCallback; -import com.android.systemui.InitController; -import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; -import com.android.systemui.animation.ActivityLaunchAnimator; -import com.android.systemui.assist.AssistManager; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.classifier.FalsingCollector; -import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dagger.qualifiers.UiBackground; -import com.android.systemui.demomode.DemoModeController; -import com.android.systemui.dreams.DreamOverlayStateController; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.fragments.FragmentService; -import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.keyguard.ScreenLifecycle; -import com.android.systemui.keyguard.WakefulnessLifecycle; -import com.android.systemui.navigationbar.NavigationBarController; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.PluginDependencyProvider; -import com.android.systemui.recents.ScreenPinningRequest; -import com.android.systemui.settings.brightness.BrightnessSliderController; -import com.android.systemui.shared.plugins.PluginManager; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.KeyguardIndicationController; -import com.android.systemui.statusbar.LockscreenShadeTransitionController; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationShadeDepthController; -import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.NotificationViewHierarchyManager; -import com.android.systemui.statusbar.PulseExpansionHandler; -import com.android.systemui.statusbar.SysuiStatusBarStateController; -import com.android.systemui.statusbar.charging.WiredChargingRippleController; -import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotifPipelineFlags; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; -import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; -import com.android.systemui.statusbar.notification.init.NotificationsController; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; -import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.phone.AutoHideController; -import com.android.systemui.statusbar.phone.BiometricUnlockController; -import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.statusbar.phone.DozeScrimController; -import com.android.systemui.statusbar.phone.DozeServiceHost; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; -import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.KeyguardDismissUtil; -import com.android.systemui.statusbar.phone.LightBarController; -import com.android.systemui.statusbar.phone.LockscreenGestureLogger; -import com.android.systemui.statusbar.phone.LockscreenWallpaper; -import com.android.systemui.statusbar.phone.NotificationIconAreaController; -import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy; -import com.android.systemui.statusbar.phone.ScreenOffAnimationController; -import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.CentralSurfaces; -import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.phone.StatusBarSignalPolicy; -import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; -import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; -import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; -import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.UserInfoControllerImpl; -import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.statusbar.window.StatusBarWindowController; -import com.android.systemui.statusbar.window.StatusBarWindowStateController; -import com.android.systemui.util.WallpaperController; -import com.android.systemui.util.concurrency.DelayableExecutor; -import com.android.systemui.util.concurrency.MessageRouter; -import com.android.systemui.volume.VolumeComponent; -import com.android.systemui.wmshell.BubblesManager; -import com.android.wm.shell.bubbles.Bubbles; -import com.android.wm.shell.startingsurface.StartingSurface; - -import java.util.Optional; -import java.util.concurrent.Executor; - -import javax.inject.Named; +import com.android.systemui.statusbar.phone.CentralSurfacesImpl; -import dagger.Lazy; +import dagger.Binds; import dagger.Module; -import dagger.Provides; /** - * Dagger Module providing {@link CentralSurfaces}. + * Dagger Module providing {@link CentralSurfacesImpl}. */ @Module public interface StatusBarPhoneModule { /** * Provides our instance of CentralSurfaces which is considered optional. */ - @Provides + @Binds @SysUISingleton - static CentralSurfaces provideCentralSurfaces( - Context context, - NotificationsController notificationsController, - FragmentService fragmentService, - LightBarController lightBarController, - AutoHideController autoHideController, - StatusBarWindowController statusBarWindowController, - StatusBarWindowStateController statusBarWindowStateController, - KeyguardUpdateMonitor keyguardUpdateMonitor, - StatusBarSignalPolicy statusBarSignalPolicy, - PulseExpansionHandler pulseExpansionHandler, - NotificationWakeUpCoordinator notificationWakeUpCoordinator, - KeyguardBypassController keyguardBypassController, - KeyguardStateController keyguardStateController, - HeadsUpManagerPhone headsUpManagerPhone, - DynamicPrivacyController dynamicPrivacyController, - FalsingManager falsingManager, - FalsingCollector falsingCollector, - BroadcastDispatcher broadcastDispatcher, - NotifShadeEventSource notifShadeEventSource, - NotificationEntryManager notificationEntryManager, - NotificationGutsManager notificationGutsManager, - NotificationLogger notificationLogger, - NotificationInterruptStateProvider notificationInterruptStateProvider, - NotificationViewHierarchyManager notificationViewHierarchyManager, - PanelExpansionStateManager panelExpansionStateManager, - KeyguardViewMediator keyguardViewMediator, - DisplayMetrics displayMetrics, - MetricsLogger metricsLogger, - @UiBackground Executor uiBgExecutor, - NotificationMediaManager notificationMediaManager, - NotificationLockscreenUserManager lockScreenUserManager, - NotificationRemoteInputManager remoteInputManager, - UserSwitcherController userSwitcherController, - NetworkController networkController, - BatteryController batteryController, - SysuiColorExtractor colorExtractor, - ScreenLifecycle screenLifecycle, - WakefulnessLifecycle wakefulnessLifecycle, - SysuiStatusBarStateController statusBarStateController, - Optional<BubblesManager> bubblesManagerOptional, - Optional<Bubbles> bubblesOptional, - VisualStabilityManager visualStabilityManager, - DeviceProvisionedController deviceProvisionedController, - NavigationBarController navigationBarController, - AccessibilityFloatingMenuController accessibilityFloatingMenuController, - Lazy<AssistManager> assistManagerLazy, - ConfigurationController configurationController, - NotificationShadeWindowController notificationShadeWindowController, - DozeParameters dozeParameters, - ScrimController scrimController, - Lazy<LockscreenWallpaper> lockscreenWallpaperLazy, - LockscreenGestureLogger lockscreenGestureLogger, - Lazy<BiometricUnlockController> biometricUnlockControllerLazy, - DozeServiceHost dozeServiceHost, - PowerManager powerManager, - ScreenPinningRequest screenPinningRequest, - DozeScrimController dozeScrimController, - VolumeComponent volumeComponent, - CommandQueue commandQueue, - CentralSurfacesComponent.Factory statusBarComponentFactory, - PluginManager pluginManager, - ShadeController shadeController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager, - ViewMediatorCallback viewMediatorCallback, - InitController initController, - @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler, - PluginDependencyProvider pluginDependencyProvider, - KeyguardDismissUtil keyguardDismissUtil, - ExtensionController extensionController, - UserInfoControllerImpl userInfoControllerImpl, - PhoneStatusBarPolicy phoneStatusBarPolicy, - KeyguardIndicationController keyguardIndicationController, - DemoModeController demoModeController, - Lazy<NotificationShadeDepthController> notificationShadeDepthController, - StatusBarTouchableRegionManager statusBarTouchableRegionManager, - NotificationIconAreaController notificationIconAreaController, - BrightnessSliderController.Factory brightnessSliderFactory, - ScreenOffAnimationController screenOffAnimationController, - WallpaperController wallpaperController, - OngoingCallController ongoingCallController, - StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, - LockscreenShadeTransitionController transitionController, - FeatureFlags featureFlags, - KeyguardUnlockAnimationController keyguardUnlockAnimationController, - @Main Handler mainHandler, - @Main DelayableExecutor delayableExecutor, - @Main MessageRouter messageRouter, - WallpaperManager wallpaperManager, - Optional<StartingSurface> startingSurfaceOptional, - ActivityLaunchAnimator activityLaunchAnimator, - NotifPipelineFlags notifPipelineFlags, - InteractionJankMonitor jankMonitor, - DeviceStateManager deviceStateManager, - DreamOverlayStateController dreamOverlayStateController, - WiredChargingRippleController wiredChargingRippleController) { - return new CentralSurfaces( - context, - notificationsController, - fragmentService, - lightBarController, - autoHideController, - statusBarWindowController, - statusBarWindowStateController, - keyguardUpdateMonitor, - statusBarSignalPolicy, - pulseExpansionHandler, - notificationWakeUpCoordinator, - keyguardBypassController, - keyguardStateController, - headsUpManagerPhone, - dynamicPrivacyController, - falsingManager, - falsingCollector, - broadcastDispatcher, - notifShadeEventSource, - notificationEntryManager, - notificationGutsManager, - notificationLogger, - notificationInterruptStateProvider, - notificationViewHierarchyManager, - panelExpansionStateManager, - keyguardViewMediator, - displayMetrics, - metricsLogger, - uiBgExecutor, - notificationMediaManager, - lockScreenUserManager, - remoteInputManager, - userSwitcherController, - networkController, - batteryController, - colorExtractor, - screenLifecycle, - wakefulnessLifecycle, - statusBarStateController, - bubblesManagerOptional, - bubblesOptional, - visualStabilityManager, - deviceProvisionedController, - navigationBarController, - accessibilityFloatingMenuController, - assistManagerLazy, - configurationController, - notificationShadeWindowController, - dozeParameters, - scrimController, - lockscreenWallpaperLazy, - lockscreenGestureLogger, - biometricUnlockControllerLazy, - dozeServiceHost, - powerManager, - screenPinningRequest, - dozeScrimController, - volumeComponent, - commandQueue, - statusBarComponentFactory, - pluginManager, - shadeController, - statusBarKeyguardViewManager, - viewMediatorCallback, - initController, - timeTickHandler, - pluginDependencyProvider, - keyguardDismissUtil, - extensionController, - userInfoControllerImpl, - phoneStatusBarPolicy, - keyguardIndicationController, - demoModeController, - notificationShadeDepthController, - statusBarTouchableRegionManager, - notificationIconAreaController, - brightnessSliderFactory, - screenOffAnimationController, - wallpaperController, - ongoingCallController, - statusBarHideIconsForBouncerManager, - transitionController, - featureFlags, - keyguardUnlockAnimationController, - mainHandler, - delayableExecutor, - messageRouter, - wallpaperManager, - startingSurfaceOptional, - activityLaunchAnimator, - notifPipelineFlags, - jankMonitor, - deviceStateManager, - dreamOverlayStateController, - wiredChargingRippleController - ); - } + CentralSurfaces bindsCentralSurfaces(CentralSurfacesImpl impl); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java index 2eba325ff63d..6717bc768fbb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java @@ -18,14 +18,12 @@ package com.android.systemui.statusbar.phone.fragment.dagger; import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.dagger.qualifiers.RootView; -import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.LightsOutNotifController; import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarDemoMode; -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import dagger.BindsInstance; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt index e29959290355..ca667dddbe8a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.phone.panelstate /** A listener interface to be notified of state change events for the notification panel. */ -interface PanelStateListener { +fun interface PanelStateListener { /** Called when the panel's expansion state has changed. */ fun onPanelStateChanged(@PanelState state: Int) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/NoOpOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/NoOpOverScroller.kt new file mode 100644 index 000000000000..2789db874249 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/NoOpOverScroller.kt @@ -0,0 +1,14 @@ +package com.android.systemui.statusbar.phone.shade.transition + +import javax.inject.Inject + +/** + * An implementation on [ShadeOverScroller] that does nothing. + * + * At the moment there is only a concrete implementation [ShadeOverScroller] for split-shade, so + * this one is used when we are not in split-shade. + */ +class NoOpOverScroller @Inject constructor() : ShadeOverScroller { + override fun onPanelStateChanged(newPanelState: Int) {} + override fun onDragDownAmountChanged(newDragDownAmount: Float) {} +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeOverScroller.kt new file mode 100644 index 000000000000..f1cedeb21e0a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeOverScroller.kt @@ -0,0 +1,11 @@ +package com.android.systemui.statusbar.phone.shade.transition + +import com.android.systemui.statusbar.phone.panelstate.PanelState + +/** Represents an over scroller for the non-lockscreen shade. */ +interface ShadeOverScroller { + + fun onPanelStateChanged(@PanelState newPanelState: Int) + + fun onDragDownAmountChanged(newDragDownAmount: Float) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt new file mode 100644 index 000000000000..2762b9a38e92 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt @@ -0,0 +1,73 @@ +package com.android.systemui.statusbar.phone.shade.transition + +import android.content.Context +import android.content.res.Configuration +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.plugins.qs.QS +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.NotificationPanelViewController +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager +import com.android.systemui.statusbar.phone.panelstate.PanelState +import com.android.systemui.statusbar.policy.ConfigurationController +import javax.inject.Inject + +/** Controls the shade expansion transition on non-lockscreen. */ +@SysUISingleton +class ShadeTransitionController +@Inject +constructor( + configurationController: ConfigurationController, + panelExpansionStateManager: PanelExpansionStateManager, + private val context: Context, + private val splitShadeOverScrollerFactory: SplitShadeOverScroller.Factory, + private val noOpOverScroller: NoOpOverScroller +) { + + lateinit var notificationPanelViewController: NotificationPanelViewController + lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController + lateinit var qs: QS + + private var inSplitShade = false + + private val splitShadeOverScroller by lazy { + splitShadeOverScrollerFactory.create(qs, notificationStackScrollLayoutController) + } + private val shadeOverScroller: ShadeOverScroller + get() = + if (inSplitShade && propertiesInitialized()) { + splitShadeOverScroller + } else { + noOpOverScroller + } + + init { + updateResources() + configurationController.addCallback( + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + updateResources() + } + }) + panelExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged) + panelExpansionStateManager.addStateListener(this::onPanelStateChanged) + } + + private fun updateResources() { + inSplitShade = context.resources.getBoolean(R.bool.config_use_split_notification_shade) + } + + private fun onPanelStateChanged(@PanelState state: Int) { + shadeOverScroller.onPanelStateChanged(state) + } + + private fun onPanelExpansionChanged(event: PanelExpansionChangeEvent) { + shadeOverScroller.onDragDownAmountChanged(event.dragDownPxAmount) + } + + private fun propertiesInitialized() = + this::qs.isInitialized && + this::notificationPanelViewController.isInitialized && + this::notificationStackScrollLayoutController.isInitialized +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScroller.kt new file mode 100644 index 000000000000..71050f2e7c67 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScroller.kt @@ -0,0 +1,142 @@ +package com.android.systemui.statusbar.phone.shade.transition + +import android.animation.Animator +import android.animation.ValueAnimator +import android.content.Context +import android.content.res.Configuration +import android.util.MathUtils +import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.R +import com.android.systemui.animation.Interpolators +import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.qs.QS +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.ScrimController +import com.android.systemui.statusbar.phone.panelstate.PanelState +import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED +import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING +import com.android.systemui.statusbar.policy.ConfigurationController +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.io.PrintWriter + +class SplitShadeOverScroller +@AssistedInject +constructor( + configurationController: ConfigurationController, + dumpManager: DumpManager, + private val context: Context, + private val scrimController: ScrimController, + @Assisted private val qS: QS, + @Assisted private val nsslController: NotificationStackScrollLayoutController +) : ShadeOverScroller { + + private var releaseOverScrollDuration = 0L + private var maxOverScrollAmount = 0 + private var previousOverscrollAmount = 0 + private var dragDownAmount: Float = 0f + @PanelState private var panelState: Int = STATE_CLOSED + private var releaseOverScrollAnimator: Animator? = null + + init { + updateResources() + configurationController.addCallback( + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + updateResources() + } + }) + dumpManager.registerDumpable(this::dump) + } + + private fun updateResources() { + val resources = context.resources + maxOverScrollAmount = resources.getDimensionPixelSize(R.dimen.shade_max_over_scroll_amount) + releaseOverScrollDuration = + resources.getInteger(R.integer.lockscreen_shade_over_scroll_release_duration).toLong() + } + + override fun onPanelStateChanged(@PanelState newPanelState: Int) { + if (shouldReleaseOverscroll(previousState = panelState, newState = newPanelState)) { + releaseOverScroll() + } + panelState = newPanelState + } + + override fun onDragDownAmountChanged(newDragDownAmount: Float) { + if (dragDownAmount == newDragDownAmount) { + return + } + dragDownAmount = newDragDownAmount + if (shouldOverscroll()) { + overScroll(newDragDownAmount) + } + } + + private fun shouldOverscroll() = panelState == STATE_OPENING + + private fun shouldReleaseOverscroll(@PanelState previousState: Int, @PanelState newState: Int) = + previousState == STATE_OPENING && newState != STATE_OPENING + + private fun overScroll(dragDownAmount: Float) { + val overscrollAmount: Int = calculateOverscrollAmount(dragDownAmount) + applyOverscroll(overscrollAmount) + previousOverscrollAmount = overscrollAmount + } + + private fun calculateOverscrollAmount(dragDownAmount: Float): Int { + val fullHeight: Int = nsslController.height + val fullHeightProgress: Float = MathUtils.saturate(dragDownAmount / fullHeight) + return (fullHeightProgress * maxOverScrollAmount).toInt() + } + + private fun applyOverscroll(overscrollAmount: Int) { + qS.setOverScrollAmount(overscrollAmount) + scrimController.setNotificationsOverScrollAmount(overscrollAmount) + nsslController.setOverScrollAmount(overscrollAmount) + } + + private fun releaseOverScroll() { + val animator = ValueAnimator.ofInt(previousOverscrollAmount, 0) + animator.addUpdateListener { + val overScrollAmount = it.animatedValue as Int + qS.setOverScrollAmount(overScrollAmount) + scrimController.setNotificationsOverScrollAmount(overScrollAmount) + nsslController.setOverScrollAmount(overScrollAmount) + } + animator.interpolator = Interpolators.STANDARD + animator.duration = releaseOverScrollDuration + animator.start() + releaseOverScrollAnimator = animator + previousOverscrollAmount = 0 + } + + @VisibleForTesting + internal fun finishAnimations() { + releaseOverScrollAnimator?.end() + releaseOverScrollAnimator = null + } + + private fun dump(pw: PrintWriter, strings: Array<String>) { + pw.println( + """ + SplitShadeOverScroller: + Resources: + releaseOverScrollDuration: $releaseOverScrollDuration + maxOverScrollAmount: $maxOverScrollAmount + State: + previousOverscrollAmount: $previousOverscrollAmount + dragDownAmount: $dragDownAmount + panelState: $panelState + """.trimIndent()) + } + + @AssistedFactory + fun interface Factory { + fun create( + qS: QS, + nsslController: NotificationStackScrollLayoutController + ): SplitShadeOverScroller + } +} diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 7920d388c670..a50d3d607aec 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -474,7 +474,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { mThemeStyle = fetchThemeStyleFromSetting(); mSecondaryOverlay = getOverlay(mMainWallpaperColor, ACCENT, mThemeStyle); mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL, mThemeStyle); - if (colorSchemeIsApplied()) { + if (colorSchemeIsApplied() && !forceReload) { Log.d(TAG, "Skipping overlay creation. Theme was already: " + mColorScheme); return; } diff --git a/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt b/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt new file mode 100644 index 000000000000..27a53bf2ceda --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import android.content.res.TypedArray +import android.graphics.Color +import android.view.ContextThemeWrapper + +/** Returns an ARGB color version of [color] at the given [alpha]. */ +fun getColorWithAlpha(color: Int, alpha: Float): Int = + Color.argb( + (alpha * 255).toInt(), + Color.red(color), + Color.green(color), + Color.blue(color) + ) + + +/** + * Returns the color provided at the specified {@param attrIndex} in {@param a} if it exists, + * otherwise, returns the color from the private attribute {@param privAttrId}. + */ +fun getPrivateAttrColorIfUnset( + ctw: ContextThemeWrapper, attrArray: TypedArray, + attrIndex: Int, defColor: Int, privAttrId: Int +): Int { + // If the index is specified, use that value + var a = attrArray + if (a.hasValue(attrIndex)) { + return a.getColor(attrIndex, defColor) + } + + // Otherwise fallback to the value of the private attribute + val customAttrs = intArrayOf(privAttrId) + a = ctw.obtainStyledAttributes(customAttrs) + val color = a.getColor(0, defColor) + a.recycle() + return color +} diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index 8e5e1d2e1b87..5b5dca30620a 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -105,25 +105,6 @@ public class Utils { } /** - * Returns the color provided at the specified {@param attrIndex} in {@param a} if it exists, - * otherwise, returns the color from the private attribute {@param privAttrId}. - */ - public static int getPrivateAttrColorIfUnset(ContextThemeWrapper ctw, TypedArray a, - int attrIndex, int defColor, int privAttrId) { - // If the index is specified, use that value - if (a.hasValue(attrIndex)) { - return a.getColor(attrIndex, defColor); - } - - // Otherwise fallback to the value of the private attribute - int[] customAttrs = { privAttrId }; - a = ctw.obtainStyledAttributes(customAttrs); - int color = a.getColor(0, defColor); - a.recycle(); - return color; - } - - /** * Gets the {@link R.dimen#status_bar_header_height_keyguard}. */ public static int getStatusBarHeaderHeightKeyguard(Context context) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java index 4beec574cd2a..01365b43b4b8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java @@ -70,7 +70,7 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase { private Display mSecondaryDisplay; // This display is in a different group from the default and secondary displays. - private Display mDifferentGroupDisplay; + private Display mAlwaysUnlockedDisplay; @Before public void setUp() { @@ -86,12 +86,12 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase { Display.DEFAULT_DISPLAY + 1, new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS); - DisplayInfo differentGroupInfo = new DisplayInfo(); - differentGroupInfo.displayId = Display.DEFAULT_DISPLAY + 2; - differentGroupInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; - mDifferentGroupDisplay = new Display(DisplayManagerGlobal.getInstance(), + DisplayInfo alwaysUnlockedDisplayInfo = new DisplayInfo(); + alwaysUnlockedDisplayInfo.displayId = Display.DEFAULT_DISPLAY + 2; + alwaysUnlockedDisplayInfo.flags = Display.FLAG_ALWAYS_UNLOCKED; + mAlwaysUnlockedDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY, - differentGroupInfo, DEFAULT_DISPLAY_ADJUSTMENTS); + alwaysUnlockedDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS); } @Test @@ -110,18 +110,18 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase { } @Test - public void testShow_includeNonDefaultGroupDisplay() { + public void testShow_includeAlwaysUnlockedDisplay() { when(mDisplayManager.getDisplays()).thenReturn( - new Display[]{mDefaultDisplay, mDifferentGroupDisplay}); + new Display[]{mDefaultDisplay, mAlwaysUnlockedDisplay}); mManager.show(); verify(mManager, never()).createPresentation(any()); } @Test - public void testShow_includeSecondaryAndNonDefaultGroupDisplays() { + public void testShow_includeSecondaryAndAlwaysUnlockedDisplays() { when(mDisplayManager.getDisplays()).thenReturn( - new Display[]{mDefaultDisplay, mSecondaryDisplay, mDifferentGroupDisplay}); + new Display[]{mDefaultDisplay, mSecondaryDisplay, mAlwaysUnlockedDisplay}); mManager.show(); verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay)); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt new file mode 100644 index 000000000000..aff94eb7aef5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.inputmethod.InputMethodManager +import androidx.test.filters.SmallTest +import com.android.internal.util.LatencyTracker +import com.android.internal.widget.LockPatternUtils +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.util.concurrency.DelayableExecutor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class KeyguardPasswordViewControllerTest : SysuiTestCase() { + @Mock + private lateinit var keyguardPasswordView: KeyguardPasswordView + @Mock + lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock + lateinit var securityMode: KeyguardSecurityModel.SecurityMode + @Mock + lateinit var lockPatternUtils: LockPatternUtils + @Mock + lateinit var keyguardSecurityCallback: KeyguardSecurityCallback + @Mock + lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory + @Mock + lateinit var latencyTracker: LatencyTracker + @Mock + lateinit var inputMethodManager: InputMethodManager + @Mock + lateinit var emergencyButtonController: EmergencyButtonController + @Mock + lateinit var mainExecutor: DelayableExecutor + @Mock + lateinit var falsingCollector: FalsingCollector + @Mock + lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager + @Mock + private lateinit var mKeyguardMessageArea: KeyguardMessageArea + @Mock + private lateinit var mKeyguardMessageAreaController: KeyguardMessageAreaController + + private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + Mockito.`when`(keyguardPasswordView + .findViewById<KeyguardMessageArea>(R.id.keyguard_message_area)) + .thenReturn(mKeyguardMessageArea) + Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea)) + .thenReturn(mKeyguardMessageAreaController) + keyguardPasswordViewController = KeyguardPasswordViewController( + keyguardPasswordView, + keyguardUpdateMonitor, + securityMode, + lockPatternUtils, + keyguardSecurityCallback, + messageAreaControllerFactory, + latencyTracker, + inputMethodManager, + emergencyButtonController, + mainExecutor, + mContext.resources, + falsingCollector, + statusBarKeyguardViewManager + ) + } + + @Test + fun testFocusWhenBouncerIsShown() { + Mockito.`when`(statusBarKeyguardViewManager.isBouncerShowing).thenReturn(true) + Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true) + keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED) + keyguardPasswordView.post { verify(keyguardPasswordView).requestFocus() } + } + + @Test + fun testDoNotFocusWhenBouncerIsHidden() { + Mockito.`when`(statusBarKeyguardViewManager.isBouncerShowing).thenReturn(false) + Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true) + keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED) + verify(keyguardPasswordView, never()).requestFocus() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index a819a7a0f815..7b7dfdce90b2 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -51,6 +51,7 @@ import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; @@ -123,6 +124,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { private UserSwitcherController mUserSwitcherController; @Mock private SessionTracker mSessionTracker; + @Mock + private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private Configuration mConfiguration; private KeyguardSecurityContainerController mKeyguardSecurityContainerController; @@ -150,7 +153,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor, SecurityMode.Password, mLockPatternUtils, null, mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController, - null, mock(Resources.class), null); + null, mock(Resources.class), null, mStatusBarKeyguardViewManager); mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory( mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils, @@ -287,4 +290,14 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, mUserSwitcherController); } + + @Test + public void addUserSwitchCallback() { + mKeyguardSecurityContainerController.onViewAttached(); + verify(mUserSwitcherController) + .addUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class)); + mKeyguardSecurityContainerController.onViewDetached(); + verify(mUserSwitcherController) + .removeUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class)); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index 1753157c631d..650a5d0a8712 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -22,7 +22,6 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import com.android.systemui.SysuiTestCase; -import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -55,8 +54,6 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { @Mock DozeParameters mDozeParameters; @Mock - KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; - @Mock ScreenOffAnimationController mScreenOffAnimationController; @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor; @@ -75,7 +72,6 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { mKeyguardUpdateMonitor, mConfigurationController, mDozeParameters, - mKeyguardUnlockAnimationController, mScreenOffAnimationController); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt index e62b4e63e3d5..55f0591b6ce0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt @@ -142,6 +142,25 @@ class DisplayCutoutBaseViewTest : SysuiTestCase() { assertThat(cutoutBaseView.protectionRect).isEqualTo(pathBounds) } + @Test + fun testCutoutProtection_withDisplayRatio() { + setupDisplayCutoutBaseView(true /* fillCutout */, false /* hasCutout */) + whenever(cutoutBaseView.getPhysicalPixelDisplaySizeRatio()).thenReturn(0.5f) + val bounds = Rect(0, 0, 10, 10) + val path = Path() + val pathBounds = RectF(bounds) + path.addRect(pathBounds, Path.Direction.CCW) + + context.mainExecutor.execute { + cutoutBaseView.setProtection(path, bounds) + cutoutBaseView.enableShowProtection(true) + } + waitForIdleSync() + + assertThat(cutoutBaseView.protectionPath.isRect(pathBounds)).isTrue() + assertThat(cutoutBaseView.protectionRect).isEqualTo(RectF(0f, 0f, 5f, 5f)) + } + private fun setupDisplayCutoutBaseView(fillCutout: Boolean, hasCutout: Boolean) { mContext.orCreateTestableResources.addOverride( R.array.config_displayUniqueIdArray, arrayOf<String>()) @@ -151,6 +170,7 @@ class DisplayCutoutBaseViewTest : SysuiTestCase() { cutoutBaseView = spy(DisplayCutoutBaseView(mContext)) whenever(cutoutBaseView.display).thenReturn(mockDisplay) whenever(cutoutBaseView.rootView).thenReturn(mockRootView) + whenever(cutoutBaseView.getPhysicalPixelDisplaySizeRatio()).thenReturn(1f) whenever(mockDisplay.getDisplayInfo(eq(cutoutBaseView.displayInfo)) ).then { val info = it.getArgument<DisplayInfo>(0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index fb1a968acceb..2d8c4d5dceb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -17,6 +17,7 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.policy.KeyguardStateController import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -125,4 +126,69 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { verify(keyguardViewMediator, times(0)).onKeyguardExitRemoteAnimationFinished( false /* cancelled */) } + + /** + * If we requested that the surface behind be made visible, and we're not flinging away the + * keyguard, it means that we're swiping to unlock and want the surface visible so it can follow + * the user's touch event as they swipe to unlock. + * + * In this case, we should verify that the surface was made visible via the alpha fade in + * animator, and verify that we did not start the canned animation to animate the surface in + * (since it's supposed to be following the touch events). + */ + @Test + fun fadeInSurfaceBehind_ifRequestedShowSurface_butNotFlinging() { + `when`(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(false) + + keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( + remoteAnimationTarget, + 0 /* startTime */, + true /* requestedShowSurfaceBehindKeyguard */ + ) + + assertTrue(keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.isRunning) + assertFalse(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) + } + + /** + * We requested the surface behind to be made visible, but we're now flinging to dismiss the + * keyguard. This means this was a swipe to dismiss gesture but the user flung the keyguard and + * lifted their finger while we were requesting the surface be made visible. + * + * In this case, we should verify that we are playing the canned unlock animation and not + * simply fading in the surface. + */ + @Test + fun playCannedUnlockAnimation_ifRequestedShowSurface_andFlinging() { + `when`(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(true) + + keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( + remoteAnimationTarget, + 0 /* startTime */, + true /* requestedShowSurfaceBehindKeyguard */ + ) + + assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) + assertFalse(keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.isRunning) + } + + /** + * We never requested the surface behind to be made visible, which means no swiping to unlock + * ever happened and we're just playing the simple canned animation (happens via UDFPS unlock, + * long press on the lock icon, etc). + * + * In this case, we should verify that we are playing the canned unlock animation and not + * simply fading in the surface. + */ + @Test + fun playCannedUnlockAnimation_ifDidNotRequestShowSurface() { + keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( + remoteAnimationTarget, + 0 /* startTime */, + false /* requestedShowSurfaceBehindKeyguard */ + ) + + assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) + assertFalse(keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.isRunning) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt index 8f967ab5294f..65d501442d87 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt @@ -19,9 +19,9 @@ package com.android.systemui.media import org.mockito.Mockito.`when` as whenever import android.animation.ValueAnimator import android.graphics.Color -import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.monet.ColorScheme import junit.framework.Assert.assertEquals @@ -46,28 +46,35 @@ class ColorSchemeTransitionTest : SysuiTestCase() { private interface ExtractCB : (ColorScheme) -> Int private interface ApplyCB : (Int) -> Unit - private lateinit var colorTransition: ColorTransition + private lateinit var colorTransition: AnimatingColorTransition private lateinit var colorSchemeTransition: ColorSchemeTransition - @Mock private lateinit var mockTransition: ColorTransition + @Mock private lateinit var mockAnimatingTransition: AnimatingColorTransition + @Mock private lateinit var mockGenericTransition: GenericColorTransition @Mock private lateinit var valueAnimator: ValueAnimator @Mock private lateinit var colorScheme: ColorScheme @Mock private lateinit var extractColor: ExtractCB @Mock private lateinit var applyColor: ApplyCB - private lateinit var transitionFactory: ColorTransitionFactory + private lateinit var animatingColorTransitionFactory: AnimatingColorTransitionFactory + private lateinit var genericColorTransitionFactory: GenericColorTransitionFactory @Mock private lateinit var mediaViewHolder: MediaViewHolder @JvmField @Rule val mockitoRule = MockitoJUnit.rule() @Before fun setUp() { - transitionFactory = { default, extractColor, applyColor -> mockTransition } + animatingColorTransitionFactory = { _, _, _ -> mockAnimatingTransition } + genericColorTransitionFactory = { _ -> mockGenericTransition } whenever(extractColor.invoke(colorScheme)).thenReturn(TARGET_COLOR) - colorSchemeTransition = ColorSchemeTransition(context, mediaViewHolder, transitionFactory) + colorSchemeTransition = ColorSchemeTransition( + context, mediaViewHolder, animatingColorTransitionFactory, genericColorTransitionFactory + ) - colorTransition = object : ColorTransition(DEFAULT_COLOR, extractColor, applyColor) { + colorTransition = object : AnimatingColorTransition( + DEFAULT_COLOR, extractColor, applyColor + ) { override fun buildAnimator(): ValueAnimator { return valueAnimator } @@ -142,6 +149,7 @@ class ColorSchemeTransitionTest : SysuiTestCase() { @Test fun testColorSchemeTransition_update() { colorSchemeTransition.updateColorScheme(colorScheme) - verify(mockTransition, times(6)).updateColorScheme(colorScheme) + verify(mockAnimatingTransition, times(6)).updateColorScheme(colorScheme) + verify(mockGenericTransition).updateColorScheme(colorScheme) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index b8c85bb41726..76171b2c4054 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -124,7 +124,6 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var collapsedSet: ConstraintSet @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory @Mock private lateinit var mediaCarouselController: MediaCarouselController - @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler @Mock private lateinit var falsingManager: FalsingManager @Mock private lateinit var transitionParent: ViewGroup private lateinit var appIcon: ImageView @@ -270,7 +269,7 @@ public class MediaControlPanelTest : SysuiTestCase() { smartspaceData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( packageName = PACKAGE, instanceId = instanceId, - recommendations = listOf(smartspaceAction), + recommendations = listOf(smartspaceAction, smartspaceAction, smartspaceAction), cardAction = smartspaceAction ) } @@ -294,9 +293,6 @@ public class MediaControlPanelTest : SysuiTestCase() { */ private fun initMediaViewHolderMocks() { whenever(seekBarViewModel.progress).thenReturn(seekBarData) - whenever(mediaCarouselController.mediaCarouselScrollHandler) - .thenReturn(mediaCarouselScrollHandler) - whenever(mediaCarouselScrollHandler.qsExpanded).thenReturn(false) // Set up mock views for the players appIcon = ImageView(context) @@ -1448,6 +1444,66 @@ public class MediaControlPanelTest : SysuiTestCase() { } @Test + fun bindRecommendation_listHasTooFewRecs_notDisplayed() { + player.attachRecommendation(recommendationViewHolder) + val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata) + val data = smartspaceData.copy( + recommendations = listOf( + SmartspaceAction.Builder("id1", "title1") + .setSubtitle("subtitle1") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "title2") + .setSubtitle("subtitle2") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + ) + ) + + player.bindRecommendation(data) + + assertThat(recTitle1.text).isEqualTo("") + verify(mediaViewController, never()).refreshState() + } + + @Test + fun bindRecommendation_listHasTooFewRecsWithIcons_notDisplayed() { + player.attachRecommendation(recommendationViewHolder) + val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata) + val data = smartspaceData.copy( + recommendations = listOf( + SmartspaceAction.Builder("id1", "title1") + .setSubtitle("subtitle1") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "title2") + .setSubtitle("subtitle2") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "empty icon 1") + .setSubtitle("subtitle2") + .setIcon(null) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "empty icon 2") + .setSubtitle("subtitle2") + .setIcon(null) + .setExtras(Bundle.EMPTY) + .build(), + ) + ) + + player.bindRecommendation(data) + + assertThat(recTitle1.text).isEqualTo("") + verify(mediaViewController, never()).refreshState() + } + + @Test fun bindRecommendation_hasTitlesAndSubtitles() { player.attachRecommendation(recommendationViewHolder) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt index 1f9490ab3851..6a532d74967f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt @@ -110,7 +110,7 @@ class MediaDataFilterTest : SysuiTestCase() { `when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY) `when`(smartspaceData.isActive).thenReturn(true) - `when`(smartspaceData.isValid).thenReturn(true) + `when`(smartspaceData.isValid()).thenReturn(true) `when`(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE) `when`(smartspaceData.recommendations).thenReturn(listOf(smartspaceMediaRecommendationItem)) `when`(smartspaceData.headphoneConnectionTimeMillis).thenReturn( @@ -196,22 +196,108 @@ class MediaDataFilterTest : SysuiTestCase() { } @Test - fun testHasAnyMediaOrRecommendation() { + fun hasAnyMedia_noMediaSet_returnsFalse() { + assertThat(mediaDataFilter.hasAnyMedia()).isFalse() + } + + @Test + fun hasAnyMedia_mediaSet_returnsTrue() { + mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain) + + assertThat(mediaDataFilter.hasAnyMedia()).isTrue() + } + + @Test + fun hasAnyMedia_recommendationSet_returnsFalse() { + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + + assertThat(mediaDataFilter.hasAnyMedia()).isFalse() + } + + @Test + fun hasAnyMediaOrRecommendation_noMediaSet_returnsFalse() { assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse() + } + @Test + fun hasAnyMediaOrRecommendation_mediaSet_returnsTrue() { mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain) + assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isTrue() - assertThat(mediaDataFilter.hasAnyMedia()).isTrue() } @Test - fun testHasActiveMediaOrRecommendation() { - assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() + fun hasAnyMediaOrRecommendation_recommendationSet_returnsTrue() { + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + + assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isTrue() + } + + @Test + fun hasActiveMedia_noMediaSet_returnsFalse() { + assertThat(mediaDataFilter.hasActiveMedia()).isFalse() + } + + @Test + fun hasActiveMedia_inactiveMediaSet_returnsFalse() { + val data = dataMain.copy(active = false) + mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data) + + assertThat(mediaDataFilter.hasActiveMedia()).isFalse() + } + + @Test + fun hasActiveMedia_activeMediaSet_returnsTrue() { val data = dataMain.copy(active = true) + mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data) + assertThat(mediaDataFilter.hasActiveMedia()).isTrue() + } + + @Test + fun hasActiveMediaOrRecommendation_nothingSet_returnsFalse() { + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() + } + + @Test + fun hasActiveMediaOrRecommendation_inactiveMediaSet_returnsFalse() { + val data = dataMain.copy(active = false) mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data) + + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() + } + + @Test + fun hasActiveMediaOrRecommendation_activeMediaSet_returnsTrue() { + val data = dataMain.copy(active = true) + mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data) + + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() + } + + @Test + fun hasActiveMediaOrRecommendation_inactiveRecommendationSet_returnsFalse() { + `when`(smartspaceData.isActive).thenReturn(false) + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() + } + + @Test + fun hasActiveMediaOrRecommendation_invalidRecommendationSet_returnsFalse() { + `when`(smartspaceData.isValid()).thenReturn(false) + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() + } + + @Test + fun hasActiveMediaOrRecommendation_activeAndValidRecommendationSet_returnsTrue() { + `when`(smartspaceData.isActive).thenReturn(true) + `when`(smartspaceData.isValid()).thenReturn(true) + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() - assertThat(mediaDataFilter.hasActiveMedia()).isTrue() } @Test @@ -332,7 +418,7 @@ class MediaDataFilterTest : SysuiTestCase() { @Test fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_activeInvalidRec_usesMedia() { - `when`(smartspaceData.isValid).thenReturn(false) + `when`(smartspaceData.isValid()).thenReturn(false) // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 0cbceb6700b4..e42ae1c2f878 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -7,6 +7,7 @@ import android.app.smartspace.SmartspaceAction import android.app.smartspace.SmartspaceTarget import android.content.Intent import android.graphics.Bitmap +import android.graphics.drawable.Icon import android.media.MediaDescription import android.media.MediaMetadata import android.media.session.MediaController @@ -30,6 +31,7 @@ import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.tuner.TunerService import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock @@ -47,6 +49,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.never import org.mockito.Mockito.reset +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.junit.MockitoJUnit @@ -95,6 +98,7 @@ class MediaDataManagerTest : SysuiTestCase() { lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget @Mock private lateinit var mediaRecommendationItem: SmartspaceAction + lateinit var validRecommendationList: List<SmartspaceAction> @Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction @Mock private lateinit var mediaFlags: MediaFlags @Mock private lateinit var logger: MediaUiEventLogger @@ -170,12 +174,17 @@ class MediaDataManagerTest : SysuiTestCase() { putString("package_name", PACKAGE_NAME) putParcelable("dismiss_intent", DISMISS_INTENT) } + val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras) whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction) whenever(mediaRecommendationItem.extras).thenReturn(recommendationExtras) + whenever(mediaRecommendationItem.icon).thenReturn(icon) + validRecommendationList = listOf( + mediaRecommendationItem, mediaRecommendationItem, mediaRecommendationItem + ) whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE) whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA) - whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf(mediaRecommendationItem)) + whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList) whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L) whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false) whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId()) @@ -505,10 +514,9 @@ class MediaDataManagerTest : SysuiTestCase() { eq(SmartspaceMediaData( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, - isValid = true, packageName = PACKAGE_NAME, cardAction = mediaSmartspaceBaseAction, - recommendations = listOf(mediaRecommendationItem), + recommendations = validRecommendationList, dismissIntent = DISMISS_INTENT, headphoneConnectionTimeMillis = 1234L, instanceId = InstanceId.fakeInstanceId(instanceId))), @@ -527,7 +535,6 @@ class MediaDataManagerTest : SysuiTestCase() { eq(EMPTY_SMARTSPACE_MEDIA_DATA.copy( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, - isValid = false, dismissIntent = DISMISS_INTENT, headphoneConnectionTimeMillis = 1234L, instanceId = InstanceId.fakeInstanceId(instanceId))), @@ -553,7 +560,6 @@ class MediaDataManagerTest : SysuiTestCase() { eq(EMPTY_SMARTSPACE_MEDIA_DATA.copy( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, - isValid = false, dismissIntent = null, headphoneConnectionTimeMillis = 1234L, instanceId = InstanceId.fakeInstanceId(instanceId))), @@ -938,6 +944,38 @@ class MediaDataManagerTest : SysuiTestCase() { eq(instanceId), eq(MediaData.PLAYBACK_CAST_REMOTE)) } + @Test + fun testPlaybackStateChange_keyExists_callsListener() { + // Notification has been added + addNotificationAndLoad() + val callbackCaptor = argumentCaptor<(String, PlaybackState) -> Unit>() + verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor) + + // Callback gets an updated state + val state = PlaybackState.Builder() + .setState(PlaybackState.STATE_PLAYING, 0L, 1f) + .build() + callbackCaptor.value.invoke(KEY, state) + + // Listener is notified of updated state + verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), + capture(mediaDataCaptor), eq(true), eq(0), eq(false)) + assertThat(mediaDataCaptor.value.isPlaying).isTrue() + } + + @Test + fun testPlaybackStateChange_keyDoesNotExist_doesNothing() { + val state = PlaybackState.Builder().build() + val callbackCaptor = argumentCaptor<(String, PlaybackState) -> Unit>() + verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor) + + // No media added with this key + + callbackCaptor.value.invoke(KEY, state) + verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), + anyBoolean()) + } + /** * Helper function to add a media notification and capture the resulting MediaData */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt index 3e98a12b4564..3d3ac836d264 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt @@ -168,16 +168,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Test fun testOnLoad_checksForResume_badService() { - // Set up MBS that will allow connection but not return valid media - val pm = mock(PackageManager::class.java) - whenever(mockContext.packageManager).thenReturn(pm) - val resolveInfo = ResolveInfo() - val serviceInfo = ServiceInfo() - serviceInfo.packageName = PACKAGE_NAME - resolveInfo.serviceInfo = serviceInfo - resolveInfo.serviceInfo.name = CLASS_NAME - val resumeInfo = listOf(resolveInfo) - whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo) + setUpMbsWithValidResolveInfo() whenever(resumeBrowser.testConnection()).thenAnswer { callbackCaptor.value.onError() @@ -213,16 +204,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Test fun testOnLoad_checksForResume_hasService() { - // Set up mocks to successfully find a MBS that returns valid media - val pm = mock(PackageManager::class.java) - whenever(mockContext.packageManager).thenReturn(pm) - val resolveInfo = ResolveInfo() - val serviceInfo = ServiceInfo() - serviceInfo.packageName = PACKAGE_NAME - resolveInfo.serviceInfo = serviceInfo - resolveInfo.serviceInfo.name = CLASS_NAME - val resumeInfo = listOf(resolveInfo) - whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo) + setUpMbsWithValidResolveInfo() val description = MediaDescription.Builder().setTitle(TITLE).build() val component = ComponentName(PACKAGE_NAME, CLASS_NAME) @@ -288,16 +270,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Test fun testGetResumeAction_restarts() { - // Set up mocks to successfully find a MBS that returns valid media - val pm = mock(PackageManager::class.java) - whenever(mockContext.packageManager).thenReturn(pm) - val resolveInfo = ResolveInfo() - val serviceInfo = ServiceInfo() - serviceInfo.packageName = PACKAGE_NAME - resolveInfo.serviceInfo = serviceInfo - resolveInfo.serviceInfo.name = CLASS_NAME - val resumeInfo = listOf(resolveInfo) - whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo) + setUpMbsWithValidResolveInfo() val description = MediaDescription.Builder().setTitle(TITLE).build() val component = ComponentName(PACKAGE_NAME, CLASS_NAME) @@ -426,4 +399,91 @@ class MediaResumeListenerTest : SysuiTestCase() { } verify(sharedPrefsEditor, times(1)).apply() } -}
\ No newline at end of file + + @Test + fun testOnMediaDataLoaded_newKeyDifferent_oldMediaBrowserDisconnected() { + setUpMbsWithValidResolveInfo() + + resumeListener.onMediaDataLoaded(key = KEY, oldKey = null, data) + executor.runAllReady() + + resumeListener.onMediaDataLoaded(key = "newKey", oldKey = KEY, data) + + verify(resumeBrowser).disconnect() + } + + @Test + fun testOnMediaDataLoaded_updatingResumptionListError_mediaBrowserDisconnected() { + setUpMbsWithValidResolveInfo() + + // Set up mocks to return with an error + whenever(resumeBrowser.testConnection()).thenAnswer { + callbackCaptor.value.onError() + } + + resumeListener.onMediaDataLoaded(key = KEY, oldKey = null, data) + executor.runAllReady() + + // Ensure we disconnect the browser + verify(resumeBrowser).disconnect() + } + + @Test + fun testOnMediaDataLoaded_trackAdded_mediaBrowserDisconnected() { + setUpMbsWithValidResolveInfo() + + // Set up mocks to return with a track added + val description = MediaDescription.Builder().setTitle(TITLE).build() + val component = ComponentName(PACKAGE_NAME, CLASS_NAME) + whenever(resumeBrowser.testConnection()).thenAnswer { + callbackCaptor.value.addTrack(description, component, resumeBrowser) + } + + resumeListener.onMediaDataLoaded(key = KEY, oldKey = null, data) + executor.runAllReady() + + // Ensure we disconnect the browser + verify(resumeBrowser).disconnect() + } + + @Test + fun testResumeAction_oldMediaBrowserDisconnected() { + setUpMbsWithValidResolveInfo() + + val description = MediaDescription.Builder().setTitle(TITLE).build() + val component = ComponentName(PACKAGE_NAME, CLASS_NAME) + whenever(resumeBrowser.testConnection()).thenAnswer { + callbackCaptor.value.addTrack(description, component, resumeBrowser) + } + + // Load media data that will require us to get the resume action + val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false) + resumeListener.onMediaDataLoaded(KEY, null, dataCopy) + executor.runAllReady() + verify(mediaDataManager, times(2)).setResumeAction(eq(KEY), capture(actionCaptor)) + + // Set up our factory to return a new browser so we can verify we disconnected the old one + val newResumeBrowser = mock(ResumeMediaBrowser::class.java) + whenever(resumeBrowserFactory.create(capture(callbackCaptor), any())) + .thenReturn(newResumeBrowser) + + // When the resume action is run + actionCaptor.value.run() + + // Then we disconnect the old one + verify(resumeBrowser).disconnect() + } + + /** Sets up mocks to successfully find a MBS that returns valid media. */ + private fun setUpMbsWithValidResolveInfo() { + val pm = mock(PackageManager::class.java) + whenever(mockContext.packageManager).thenReturn(pm) + val resolveInfo = ResolveInfo() + val serviceInfo = ServiceInfo() + serviceInfo.packageName = PACKAGE_NAME + resolveInfo.serviceInfo = serviceInfo + resolveInfo.serviceInfo.name = CLASS_NAME + val resumeInfo = listOf(resolveInfo) + whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt index 60cbb1754db6..91c0cc2ff891 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt @@ -65,6 +65,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Mock private lateinit var logger: MediaTimeoutLogger private lateinit var executor: FakeExecutor @Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit + @Mock private lateinit var stateCallback: (String, PlaybackState) -> Unit @Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback> @JvmField @Rule val mockito = MockitoJUnit.rule() private lateinit var metadataBuilder: MediaMetadata.Builder @@ -80,6 +81,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { executor = FakeExecutor(FakeSystemClock()) mediaTimeoutListener = MediaTimeoutListener(mediaControllerFactory, executor, logger) mediaTimeoutListener.timeoutCallback = timeoutCallback + mediaTimeoutListener.stateCallback = stateCallback // Create a media session and notification for testing. metadataBuilder = MediaMetadata.Builder().apply { @@ -368,4 +370,169 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // THEN the timeout runnable is cancelled assertThat(executor.numPending()).isEqualTo(0) } + + @Test + fun testOnMediaDataLoaded_playbackActionsChanged_noCallback() { + // Load media data once + val pausedState = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PAUSE) + .build() + loadMediaDataWithPlaybackState(pausedState) + + // When media data is loaded again, with different actions + val playingState = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PLAY) + .build() + loadMediaDataWithPlaybackState(playingState) + + // Then the callback is not invoked + verify(stateCallback, never()).invoke(eq(KEY), any()) + } + + @Test + fun testOnPlaybackStateChanged_playbackActionsChanged_sendsCallback() { + // Load media data once + val pausedState = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PAUSE) + .build() + loadMediaDataWithPlaybackState(pausedState) + + // When the playback state changes, and has different actions + val playingState = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PLAY) + .build() + mediaCallbackCaptor.value.onPlaybackStateChanged(playingState) + + // Then the callback is invoked + verify(stateCallback).invoke(eq(KEY), eq(playingState!!)) + } + + @Test + fun testOnPlaybackStateChanged_differentCustomActions_sendsCallback() { + val customOne = PlaybackState.CustomAction.Builder( + "ACTION_1", + "custom action 1", + android.R.drawable.ic_media_ff) + .build() + val pausedState = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PAUSE) + .addCustomAction(customOne) + .build() + loadMediaDataWithPlaybackState(pausedState) + + // When the playback state actions change + val customTwo = PlaybackState.CustomAction.Builder( + "ACTION_2", + "custom action 2", + android.R.drawable.ic_media_rew) + .build() + val pausedStateTwoActions = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PAUSE) + .addCustomAction(customOne) + .addCustomAction(customTwo) + .build() + mediaCallbackCaptor.value.onPlaybackStateChanged(pausedStateTwoActions) + + // Then the callback is invoked + verify(stateCallback).invoke(eq(KEY), eq(pausedStateTwoActions!!)) + } + + @Test + fun testOnPlaybackStateChanged_sameActions_noCallback() { + val stateWithActions = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PLAY) + .build() + loadMediaDataWithPlaybackState(stateWithActions) + + // When the playback state updates with the same actions + mediaCallbackCaptor.value.onPlaybackStateChanged(stateWithActions) + + // Then the callback is not invoked again + verify(stateCallback, never()).invoke(eq(KEY), any()) + } + + @Test + fun testOnPlaybackStateChanged_sameCustomActions_noCallback() { + val actionName = "custom action" + val actionIcon = android.R.drawable.ic_media_ff + val customOne = PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon) + .build() + val stateOne = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PAUSE) + .addCustomAction(customOne) + .build() + loadMediaDataWithPlaybackState(stateOne) + + // When the playback state is updated, but has the same actions + val customTwo = PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon) + .build() + val stateTwo = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PAUSE) + .addCustomAction(customTwo) + .build() + mediaCallbackCaptor.value.onPlaybackStateChanged(stateTwo) + + // Then the callback is not invoked + verify(stateCallback, never()).invoke(eq(KEY), any()) + } + + @Test + fun testOnMediaDataLoaded_isPlayingChanged_noCallback() { + // Load media data in paused state + val pausedState = PlaybackState.Builder() + .setState(PlaybackState.STATE_PAUSED, 0L, 0f) + .build() + loadMediaDataWithPlaybackState(pausedState) + + // When media data is loaded again but playing + val playingState = PlaybackState.Builder() + .setState(PlaybackState.STATE_PLAYING, 0L, 1f) + .build() + loadMediaDataWithPlaybackState(playingState) + + // Then the callback is not invoked + verify(stateCallback, never()).invoke(eq(KEY), any()) + } + + @Test + fun testOnPlaybackStateChanged_isPlayingChanged_sendsCallback() { + // Load media data in paused state + val pausedState = PlaybackState.Builder() + .setState(PlaybackState.STATE_PAUSED, 0L, 0f) + .build() + loadMediaDataWithPlaybackState(pausedState) + + // When the playback state changes to playing + val playingState = PlaybackState.Builder() + .setState(PlaybackState.STATE_PLAYING, 0L, 1f) + .build() + mediaCallbackCaptor.value.onPlaybackStateChanged(playingState) + + // Then the callback is invoked + verify(stateCallback).invoke(eq(KEY), eq(playingState!!)) + } + + @Test + fun testOnPlaybackStateChanged_isPlayingSame_noCallback() { + // Load media data in paused state + val pausedState = PlaybackState.Builder() + .setState(PlaybackState.STATE_PAUSED, 0L, 0f) + .build() + loadMediaDataWithPlaybackState(pausedState) + + // When the playback state is updated, but still not playing + val playingState = PlaybackState.Builder() + .setState(PlaybackState.STATE_STOPPED, 0L, 0f) + .build() + mediaCallbackCaptor.value.onPlaybackStateChanged(playingState) + + // Then the callback is not invoked + verify(stateCallback, never()).invoke(eq(KEY), eq(playingState!!)) + } + + private fun loadMediaDataWithPlaybackState(state: PlaybackState) { + `when`(mediaController.playbackState).thenReturn(state) + mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + verify(mediaController).registerCallback(capture(mediaCallbackCaptor)) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt index dfa7c66b38f9..06d45de699e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt @@ -63,6 +63,7 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { @Mock lateinit var callback: ResumeMediaBrowser.Callback @Mock lateinit var listener: MediaResumeListener @Mock lateinit var service: MediaBrowserService + @Mock lateinit var logger: ResumeMediaBrowserLogger @Mock lateinit var browserFactory: MediaBrowserFactory @Mock lateinit var browser: MediaBrowser @Mock lateinit var token: MediaSession.Token @@ -81,8 +82,14 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { whenever(mediaController.transportControls).thenReturn(transportControls) - resumeBrowser = TestableResumeMediaBrowser(context, callback, component, browserFactory, - mediaController) + resumeBrowser = TestableResumeMediaBrowser( + context, + callback, + component, + browserFactory, + logger, + mediaController + ) } @Test @@ -282,8 +289,9 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { callback: Callback, componentName: ComponentName, browserFactory: MediaBrowserFactory, + logger: ResumeMediaBrowserLogger, private val fakeController: MediaController - ) : ResumeMediaBrowser(context, callback, componentName, browserFactory) { + ) : ResumeMediaBrowser(context, callback, componentName, browserFactory, logger) { override fun createMediaController(token: MediaSession.Token): MediaController { return fakeController diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt new file mode 100644 index 000000000000..b5078bc37b84 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt @@ -0,0 +1,108 @@ +package com.android.systemui.media + +import android.app.smartspace.SmartspaceAction +import android.graphics.drawable.Icon +import androidx.test.filters.SmallTest +import com.android.internal.logging.InstanceId +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +@SmallTest +class SmartspaceMediaDataTest : SysuiTestCase() { + + private val icon: Icon = Icon.createWithResource(context, R.drawable.ic_media_play) + + @Test + fun getValidRecommendations_onlyReturnsRecsWithIcons() { + val withIcon1 = SmartspaceAction.Builder("id", "title").setIcon(icon).build() + val withIcon2 = SmartspaceAction.Builder("id", "title").setIcon(icon).build() + val withoutIcon1 = SmartspaceAction.Builder("id", "title").setIcon(null).build() + val withoutIcon2 = SmartspaceAction.Builder("id", "title").setIcon(null).build() + val recommendations = listOf(withIcon1, withoutIcon1, withIcon2, withoutIcon2) + + val data = DEFAULT_DATA.copy(recommendations = recommendations) + + assertThat(data.getValidRecommendations()).isEqualTo(listOf(withIcon1, withIcon2)) + } + + @Test + fun isValid_emptyList_returnsFalse() { + val data = DEFAULT_DATA.copy(recommendations = listOf()) + + assertThat(data.isValid()).isFalse() + } + + @Test + fun isValid_tooFewRecs_returnsFalse() { + val data = DEFAULT_DATA.copy( + recommendations = listOf( + SmartspaceAction.Builder("id", "title").setIcon(icon).build() + ) + ) + + assertThat(data.isValid()).isFalse() + } + + @Test + fun isValid_tooFewRecsWithIcons_returnsFalse() { + val recommendations = mutableListOf<SmartspaceAction>() + // Add one fewer recommendation w/ icon than the number required + for (i in 1 until NUM_REQUIRED_RECOMMENDATIONS) { + recommendations.add( + SmartspaceAction.Builder("id", "title").setIcon(icon).build() + ) + } + for (i in 1 until 3) { + recommendations.add( + SmartspaceAction.Builder("id", "title").setIcon(null).build() + ) + } + + val data = DEFAULT_DATA.copy(recommendations = recommendations) + + assertThat(data.isValid()).isFalse() + } + + @Test + fun isValid_enoughRecsWithIcons_returnsTrue() { + val recommendations = mutableListOf<SmartspaceAction>() + // Add the number of required recommendations + for (i in 0 until NUM_REQUIRED_RECOMMENDATIONS) { + recommendations.add( + SmartspaceAction.Builder("id", "title").setIcon(icon).build() + ) + } + + val data = DEFAULT_DATA.copy(recommendations = recommendations) + + assertThat(data.isValid()).isTrue() + } + + @Test + fun isValid_manyRecsWithIcons_returnsTrue() { + val recommendations = mutableListOf<SmartspaceAction>() + // Add more than enough recommendations + for (i in 0 until NUM_REQUIRED_RECOMMENDATIONS + 3) { + recommendations.add( + SmartspaceAction.Builder("id", "title").setIcon(icon).build() + ) + } + + val data = DEFAULT_DATA.copy(recommendations = recommendations) + + assertThat(data.isValid()).isTrue() + } +} + +private val DEFAULT_DATA = SmartspaceMediaData( + targetId = "INVALID", + isActive = false, + packageName = "INVALID", + cardAction = null, + recommendations = emptyList(), + dismissIntent = null, + headphoneConnectionTimeMillis = 0, + instanceId = InstanceId.fakeInstanceId(-1) +) diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java index 51088b1d929b..863484b62a00 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java @@ -62,6 +62,16 @@ public class ColorSchemeTest extends SysuiTestCase { Assert.assertEquals(rankedSeedColors, List.of(0xffaec00a)); } + @Test + public void testStyleApplied() { + WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xffaec00a), + null, null); + // Expressive applies hue rotations to the theme color. The input theme color has hue + // 117, ensuring the hue changed significantly is a strong signal styles are being applied. + ColorScheme colorScheme = new ColorScheme(wallpaperColors, false, Style.EXPRESSIVE); + Assert.assertEquals(Cam.fromInt(colorScheme.getAccent1().get(6)).getHue(), 357.46, 0.1); + } + @Test public void testFiltersInvalidColors() { @@ -123,7 +133,7 @@ public class ColorSchemeTest extends SysuiTestCase { Style.VIBRANT /* style */); int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2); Cam cam = Cam.fromInt(neutralMid); - Assert.assertTrue(cam.getChroma() <= 8.0); + Assert.assertTrue("chroma was " + cam.getChroma(), Math.floor(cam.getChroma()) <= 10.0); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java index 2fe7c075bc18..e01ebbdda374 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java @@ -127,6 +127,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); protected Handler mMainHandler; protected FeatureFlags mFeatureFlags; + protected WifiStatusTrackerFactory mWifiStatusTrackerFactory; protected int mSubId; @@ -220,12 +221,14 @@ public class NetworkControllerBaseTest extends SysuiTestCase { return null; }).when(mMockProvisionController).addCallback(any()); + mWifiStatusTrackerFactory = new WifiStatusTrackerFactory( + mContext, mMockWm, mMockNsm, mMockCm, mMainHandler); + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mTelephonyListenerManager, mMockWm, - mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), @@ -238,6 +241,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mMockBd, mDemoModeController, mCarrierConfigTracker, + mWifiStatusTrackerFactory, mMainHandler, mFeatureFlags, mock(DumpManager.class) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java index ccfa1b31b799..3a0c203f76e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java @@ -127,11 +127,13 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { mConfig.show4gForLte = true; mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mTelephonyListenerManager, mMockWm, - mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mFakeExecutor, mCallbackHandler, + mMockSm, mConfig, Looper.getMainLooper(), mFakeExecutor, mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, mDemoModeController, - mock(CarrierConfigTracker.class), new Handler(TestableLooper.get(this).getLooper()), + mock(CarrierConfigTracker.class), + mWifiStatusTrackerFactory, + new Handler(TestableLooper.get(this).getLooper()), mFeatureFlags, mock(DumpManager.class)); setupNetworkController(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java index b84750aa7ea5..ae1b3d1e1f42 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java @@ -71,7 +71,6 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { mMockTm, mTelephonyListenerManager, mMockWm, - mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), @@ -84,6 +83,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { mMockBd, mDemoModeController, mCarrierConfigTracker, + mWifiStatusTrackerFactory, mMainHandler, mFeatureFlags, mock(DumpManager.class) @@ -105,7 +105,6 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { mMockTm, mTelephonyListenerManager, mMockWm, - mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), @@ -118,6 +117,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { mMockBd, mDemoModeController, mCarrierConfigTracker, + mWifiStatusTrackerFactory, mMainHandler, mFeatureFlags, mock(DumpManager.class) @@ -134,11 +134,12 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { when(mMockTm.isDataCapable()).thenReturn(false); // Create a new NetworkController as this is currently handled in constructor. mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, - mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig, + mTelephonyListenerManager, mMockWm, mMockSm, mConfig, Looper.getMainLooper(), mFakeExecutor, mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, mDemoModeController, mock(CarrierConfigTracker.class), + mWifiStatusTrackerFactory, mMainHandler, mFeatureFlags, mock(DumpManager.class)); setupNetworkController(); @@ -156,11 +157,12 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { when(mMockSm.getCompleteActiveSubscriptionInfoList()).thenReturn(Collections.emptyList()); mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, - mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig, + mTelephonyListenerManager, mMockWm, mMockSm, mConfig, Looper.getMainLooper(), mFakeExecutor, mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, mDemoModeController, mock(CarrierConfigTracker.class), + mWifiStatusTrackerFactory, mMainHandler, mFeatureFlags, mock(DumpManager.class)); mNetworkController.registerListeners(); @@ -225,11 +227,12 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { when(mMockTm.isDataCapable()).thenReturn(false); // Create a new NetworkController as this is currently handled in constructor. mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, - mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig, + mTelephonyListenerManager, mMockWm, mMockSm, mConfig, Looper.getMainLooper(), mFakeExecutor, mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, mDemoModeController, mock(CarrierConfigTracker.class), + mWifiStatusTrackerFactory, mMainHandler, mFeatureFlags, mock(DumpManager.class)); setupNetworkController(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt index ce58a6c82142..64aa7fb57da8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt @@ -128,6 +128,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { private val execution = FakeExecution() private val fakeParent = FrameLayout(context) private val fakePrivateLockscreenSettingUri = Uri.Builder().appendPath("test").build() + private val fakeNotifOnLockscreenSettingUri = Uri.Builder().appendPath("notif").build() private val userHandlePrimary: UserHandle = UserHandle(0) private val userHandleManaged: UserHandle = UserHandle(2) @@ -149,6 +150,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { `when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING)) .thenReturn(fakePrivateLockscreenSettingUri) + `when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING)) + .thenReturn(fakeNotifOnLockscreenSettingUri) `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession) `when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView()) `when`(userTracker.userProfiles).thenReturn(userList) @@ -160,6 +163,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { setAllowPrivateNotifications(userHandlePrimary, true) setAllowPrivateNotifications(userHandleManaged, true) setAllowPrivateNotifications(userHandleSecondary, true) + setShowNotifications(userHandlePrimary, true) controller = LockscreenSmartspaceController( context, @@ -341,6 +345,26 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test + fun testAllTargetsAreFilteredExceptWeatherWhenNotificationsAreDisabled() { + // GIVEN the active user doesn't allow any notifications on lockscreen + setShowNotifications(userHandlePrimary, false) + connectSession() + + // WHEN we receive a list of targets + val targets = listOf( + makeTarget(1, userHandlePrimary, isSensitive = true), + makeTarget(2, userHandlePrimary), + makeTarget(3, userHandleManaged), + makeTarget(4, userHandlePrimary, featureType = SmartspaceTarget.FEATURE_WEATHER) + ) + + sessionListener.onTargetsAvailable(targets) + + // THEN all non-sensitive content is still shown + verify(plugin).onTargetsAvailable(eq(listOf(targets[3]))) + } + + @Test fun testSensitiveTargetsAreFilteredOutForAppropriateUsers() { // GIVEN the active and managed users don't allow sensitive lockscreen content setAllowPrivateNotifications(userHandlePrimary, false) @@ -391,6 +415,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { fun testRecognizeSwitchToSecondaryUser() { // GIVEN an inactive secondary user that doesn't allow sensitive content setAllowPrivateNotifications(userHandleSecondary, false) + setShowNotifications(userHandleSecondary, true) connectSession() // WHEN the secondary user becomes the active user @@ -518,13 +543,15 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { fun makeTarget( id: Int, userHandle: UserHandle, - isSensitive: Boolean = false + isSensitive: Boolean = false, + featureType: Int = 0 ): SmartspaceTarget { return SmartspaceTarget.Builder( "target$id", ComponentName("testpackage", "testclass$id"), userHandle) .setSensitive(isSensitive) + .setFeatureType(featureType) .build() } @@ -536,6 +563,14 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { ).thenReturn(if (value) 1 else 0) } + private fun setShowNotifications(user: UserHandle, value: Boolean) { + `when`(secureSettings.getIntForUser( + eq(NOTIF_ON_LOCKSCREEN_SETTING), + anyInt(), + eq(user.identifier)) + ).thenReturn(if (value) 1 else 0) + } + private fun createSmartspaceView(): SmartspaceView { return spy(object : View(context), SmartspaceView { override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) { @@ -574,3 +609,5 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { private const val PRIVATE_LOCKSCREEN_SETTING = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS +private const val NOTIF_ON_LOCKSCREEN_SETTING = + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 7638452eaf90..9ea1813377a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -1439,14 +1439,16 @@ public class ShadeListBuilderTest extends SysuiTestCase { // WHEN a new child is added and the old one gets filtered while group changes are disabled. mStabilityManager.setAllowGroupChanges(false); + mStabilityManager.setAllowGroupPruning(false); mFinalizeFilter.mIndicesToFilter.add(1); addGroupChild(2, PACKAGE_1, GROUP_1); dispatchBuild(); // THEN the new child should be shown without a group + // (Note that this is the same as the expected result if there were no stability rules.) verifyBuiltList( - notif(2) // previously promoted child + notif(2) // new child ); } @@ -1489,25 +1491,6 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Test - public void testFinalizeFilteredChildrenPromotesSummary() { - // GIVEN a group with only one child was already drawn - addGroupSummary(0, PACKAGE_1, GROUP_1); - addGroupChild(1, PACKAGE_1, GROUP_1); - addGroupChild(2, PACKAGE_1, GROUP_1); - - // WHEN the parent is filtered out at the finalize step - mFinalizeFilter.mIndicesToFilter.add(1); - mFinalizeFilter.mIndicesToFilter.add(2); - - dispatchBuild(); - - // THEN the children should be promoted to the top level - verifyBuiltList( - notif(0) - ); - } - - @Test public void testFinalizeFilteredChildPromotesSibling() { // GIVEN a group with only one child was already drawn addGroupSummary(0, PACKAGE_1, GROUP_1); @@ -2269,6 +2252,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { private static class TestableStabilityManager extends NotifStabilityManager { boolean mAllowPipelineRun = true; boolean mAllowGroupChanges = true; + boolean mAllowGroupPruning = true; boolean mAllowSectionChanges = true; boolean mAllowEntryReodering = true; @@ -2281,6 +2265,11 @@ public class ShadeListBuilderTest extends SysuiTestCase { return this; } + TestableStabilityManager setAllowGroupPruning(boolean allowGroupPruning) { + mAllowGroupPruning = allowGroupPruning; + return this; + } + TestableStabilityManager setAllowSectionChanges(boolean allowSectionChanges) { mAllowSectionChanges = allowSectionChanges; return this; @@ -2311,6 +2300,11 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Override + public boolean isGroupPruneAllowed(@NonNull GroupEntry entry) { + return mAllowGroupPruning; + } + + @Override public boolean isSectionChangeAllowed(@NonNull NotificationEntry entry) { return mAllowSectionChanges; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java index 6f8e5d8e514e..f3aa20ba4527 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java @@ -35,6 +35,8 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.notification.collection.GroupEntry; +import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; @@ -83,6 +85,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { private NotifPanelEvents.Listener mNotifPanelEventsCallback; private NotifStabilityManager mNotifStabilityManager; private NotificationEntry mEntry; + private GroupEntry mGroupEntry; @Before public void setUp() { @@ -117,6 +120,10 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { .setPkg("testPkg1") .build(); + mGroupEntry = new GroupEntryBuilder() + .setSummary(mEntry) + .build(); + when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(false); // Whenever we invalidate, the pipeline runs again, so we invalidate the state @@ -135,6 +142,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { // THEN group changes are allowed assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); + assertTrue(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); // THEN section changes are allowed assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); @@ -149,6 +157,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { // THEN group changes are allowed assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); + assertTrue(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); // THEN section changes are allowed assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); @@ -163,6 +172,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { // THEN group changes are NOT allowed assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); + assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); // THEN section changes are NOT allowed assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); @@ -176,6 +186,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { // THEN group changes are NOT allowed assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); + assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); // THEN section changes are NOT allowed assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); @@ -190,6 +201,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { // THEN group changes are NOT allowed assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); + assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); // THEN section changes are NOT allowed assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); @@ -208,6 +220,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { // THEN group changes aren't allowed assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); + assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); // THEN section changes are allowed for this notification but not other notifications assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); @@ -321,6 +334,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { setPanelExpanded(true); assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); + assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); // WHEN the panel isn't expanded anymore setPanelExpanded(false); @@ -422,6 +436,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { setPanelExpanded(true); setPulsing(true); assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); + assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); // GIVEN mEntry is a HUN @@ -431,6 +446,8 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); + // BUT pruning the group for which this is the summary would still NOT be allowed. + assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); } private void setActivityLaunching(boolean activityLaunching) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 6409967aca9b..94a93ad6cf33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -74,6 +74,7 @@ import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.ZenModeController; @@ -135,6 +136,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private StackStateLogger mStackLogger; @Mock private NotificationStackScrollLogger mLogger; @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator; + @Mock private ShadeTransitionController mShadeTransitionController; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor; @@ -179,6 +181,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mNotifCollection, mEntryManager, mLockscreenShadeTransitionController, + mShadeTransitionController, mIStatusBarService, mUiEventLogger, mLayoutInflater, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt index c3658ba11b2d..663490ebfde0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt @@ -49,7 +49,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController @Mock private lateinit var stackLayout: NotificationStackScrollLayout - private val testableResources = mContext.getOrCreateTestableResources() + private val testableResources = mContext.orCreateTestableResources private lateinit var sizeCalculator: NotificationStackSizeCalculator @@ -121,17 +121,16 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { } @Test - fun computeHeight_returnsAtMostSpaceAvailable_withGapBeforeShelf() { + fun computeHeight_gapBeforeShelf_returnsSpaceUsed() { + // Each row in separate section. setGapHeight(gapHeight) - val shelfHeight = shelfHeight - val availableSpace = + val spaceUsed = listOf( - rowHeight + dividerHeight, - gapHeight + rowHeight + dividerHeight, - gapHeight + dividerHeight + shelfHeight) + dividerHeight + rowHeight, + dividerHeight + gapHeight + rowHeight, + dividerHeight + gapHeight + shelfHeight) .sum() - - // All rows in separate sections (default setup). + val availableSpace = spaceUsed + 1; val rows = listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight)) @@ -139,23 +138,29 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { assertThat(maxNotifications).isEqualTo(2) val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) - assertThat(height).isAtMost(availableSpace) + assertThat(height).isEqualTo(spaceUsed) } @Test - fun computeHeight_noGapBeforeShelf_returnsAtMostSpaceAvailable() { + fun computeHeight_noGapBeforeShelf_returnsSpaceUsed() { // Both rows are in the same section. setGapHeight(0f) + val rowHeight = rowHeight val shelfHeight = shelfHeight - val availableSpace = listOf(rowHeight + dividerHeight, dividerHeight + shelfHeight).sum() + val spaceUsed = + listOf( + dividerHeight + rowHeight, + dividerHeight + shelfHeight) + .sum() + val availableSpace = spaceUsed + 1 val rows = listOf(createMockRow(rowHeight), createMockRow(rowHeight)) val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight) assertThat(maxNotifications).isEqualTo(1) val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) - assertThat(height).isAtMost(availableSpace) + assertThat(height).isEqualTo(spaceUsed) } @Test @@ -190,7 +195,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { val space = sizeCalculator.spaceNeeded(expandableView, visibleIndex = 0, previousView = null, stack = stackLayout, onLockscreen = true) - assertThat(space).isEqualTo(5) + assertThat(space).isEqualTo(5 + dividerHeight) } @Test @@ -204,7 +209,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { val space = sizeCalculator.spaceNeeded(expandableView, visibleIndex = 0, previousView = null, stack = stackLayout, onLockscreen = false) - assertThat(space).isEqualTo(10) + assertThat(space).isEqualTo(10 + dividerHeight) } private fun computeMaxKeyguardNotifications( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 84edabdaa302..d364505445f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -178,12 +178,12 @@ import dagger.Lazy; @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) -public class CentralSurfacesTest extends SysuiTestCase { +public class CentralSurfacesImplTest extends SysuiTestCase { private static final int FOLD_STATE_FOLDED = 0; private static final int FOLD_STATE_UNFOLDED = 1; - private CentralSurfaces mCentralSurfaces; + private CentralSurfacesImpl mCentralSurfaces; private FakeMetricsLogger mMetricsLogger; private PowerManager mPowerManager; private TestableNotificationInterruptStateProviderImpl mNotificationInterruptStateProvider; @@ -383,7 +383,7 @@ public class CentralSurfacesTest extends SysuiTestCase { when(mOperatorNameViewControllerFactory.create(any())) .thenReturn(mOperatorNameViewController); - mCentralSurfaces = new CentralSurfaces( + mCentralSurfaces = new CentralSurfacesImpl( mContext, mNotificationsController, mock(FragmentService.class), @@ -479,7 +479,7 @@ public class CentralSurfacesTest extends SysuiTestCase { mDreamOverlayStateController, mWiredChargingRippleController); when(mKeyguardViewMediator.registerCentralSurfaces( - any(CentralSurfaces.class), + any(CentralSurfacesImpl.class), any(NotificationPanelViewController.class), any(PanelExpansionStateManager.class), any(BiometricUnlockController.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt new file mode 100644 index 000000000000..2ff6dd43e84e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.phone + +import android.service.notification.StatusBarNotification +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.StatusBarIconView +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` as whenever + +/** + * Tests for {@link NotificationIconContainer}. + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotificationIconContainerTest : SysuiTestCase() { + + private val iconContainer = NotificationIconContainer(context, /* attrs= */ null) + + @Test + fun calculateWidthFor_zeroIcons_widthIsZero() { + assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 0f), + /* actual= */ 0f) + } + + @Test + fun calculateWidthFor_oneIcon_widthForOneIcon() { + iconContainer.setActualPaddingStart(10f) + iconContainer.setActualPaddingEnd(10f) + iconContainer.setIconSize(10); + + assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 1f), + /* actual= */ 30f) + } + + @Test + fun calculateWidthFor_fourIcons_widthForFourIcons() { + iconContainer.setActualPaddingStart(10f) + iconContainer.setActualPaddingEnd(10f) + iconContainer.setIconSize(10); + + assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 4f), + /* actual= */ 60f) + } + + @Test + fun calculateWidthFor_fiveIcons_widthForFourIcons() { + iconContainer.setActualPaddingStart(10f) + iconContainer.setActualPaddingEnd(10f) + iconContainer.setIconSize(10); + assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 5f), + /* actual= */ 60f) + } + + @Test + fun calculateIconXTranslations_shortShelfOneIcon_atCorrectXWithoutOverflowDot() { + iconContainer.setActualPaddingStart(10f) + iconContainer.setActualPaddingEnd(10f) + iconContainer.setIconSize(10); + + val icon = mockStatusBarIcon() + iconContainer.addView(icon) + assertEquals(1, iconContainer.childCount) + + val iconState = iconContainer.getIconState(icon) + iconState.iconAppearAmount = 1f + + val width = iconContainer.calculateWidthFor(/* numIcons= */ 1f) + iconContainer.setActualLayoutWidth(width.toInt()) + + iconContainer.calculateIconXTranslations() + assertEquals(10f, iconState.xTranslation) + assertFalse(iconContainer.hasOverflow()) + } + + @Test + fun calculateIconXTranslations_shortShelfFourIcons_atCorrectXWithoutOverflowDot() { + iconContainer.setActualPaddingStart(10f) + iconContainer.setActualPaddingEnd(10f) + iconContainer.setIconSize(10); + + val iconOne = mockStatusBarIcon() + val iconTwo = mockStatusBarIcon() + val iconThree = mockStatusBarIcon() + val iconFour = mockStatusBarIcon() + + iconContainer.addView(iconOne) + iconContainer.addView(iconTwo) + iconContainer.addView(iconThree) + iconContainer.addView(iconFour) + assertEquals(4, iconContainer.childCount) + + val width = iconContainer.calculateWidthFor(/* numIcons= */ 4f) + iconContainer.setActualLayoutWidth(width.toInt()) + + iconContainer.calculateIconXTranslations() + assertEquals(10f, iconContainer.getIconState(iconOne).xTranslation) + assertEquals(20f, iconContainer.getIconState(iconTwo).xTranslation) + assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation) + assertEquals(40f, iconContainer.getIconState(iconFour).xTranslation) + + assertFalse(iconContainer.hasOverflow()) + } + + @Test + fun calculateIconXTranslations_shortShelfFiveIcons_atCorrectXWithOverflowDot() { + iconContainer.setActualPaddingStart(10f) + iconContainer.setActualPaddingEnd(10f) + iconContainer.setIconSize(10); + + val iconOne = mockStatusBarIcon() + val iconTwo = mockStatusBarIcon() + val iconThree = mockStatusBarIcon() + val iconFour = mockStatusBarIcon() + val iconFive = mockStatusBarIcon() + + iconContainer.addView(iconOne) + iconContainer.addView(iconTwo) + iconContainer.addView(iconThree) + iconContainer.addView(iconFour) + iconContainer.addView(iconFive) + assertEquals(5, iconContainer.childCount) + + val width = iconContainer.calculateWidthFor(/* numIcons= */ 5f) + iconContainer.setActualLayoutWidth(width.toInt()) + + iconContainer.calculateIconXTranslations() + assertEquals(10f, iconContainer.getIconState(iconOne).xTranslation) + assertEquals(20f, iconContainer.getIconState(iconTwo).xTranslation) + assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation) + assertTrue(iconContainer.hasOverflow()) + } + + private fun mockStatusBarIcon() : StatusBarIconView { + val iconView = mock(StatusBarIconView::class.java) + whenever(iconView.width).thenReturn(10) + + val icon = mock(android.graphics.drawable.Icon::class.java) + whenever(iconView.sourceIcon).thenReturn(icon) + + val sbn = mock(StatusBarNotification::class.java) + whenever(sbn.groupKey).thenReturn("groupKey") + whenever(iconView.notification).thenReturn(sbn) + return iconView + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index f51c428be28a..6997b3e4bd70 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -128,6 +128,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; +import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -337,6 +338,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; @Mock + private ShadeTransitionController mShadeTransitionController; + @Mock private QS mQs; @Mock private View mQsHeader; @@ -529,7 +532,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mNotificationListContainer, mPanelEventsEmitter, mNotificationStackSizeCalculator, - mUnlockedScreenOffAnimationController); + mUnlockedScreenOffAnimationController, + mShadeTransitionController); mNotificationPanelViewController.initDependencies( mCentralSurfaces, () -> {}, @@ -591,6 +595,27 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test + public void getLockscreenSpaceForNotifications_includesOverlapWithLockIcon() { + when(mResources.getDimensionPixelSize(R.dimen.keyguard_indication_bottom_padding)) + .thenReturn(0); + mNotificationPanelViewController.setAmbientIndicationTop( + /* ambientIndicationTop= */ 0, /* ambientTextVisible */ false); + + // Use lock icon padding (100 - 80 - 5 = 15) as bottom padding + when(mNotificationStackScrollLayoutController.getBottom()).thenReturn(100); + when(mLockIconViewController.getTop()).thenReturn(80f); + when(mResources.getDimensionPixelSize(R.dimen.shelf_and_lock_icon_overlap)).thenReturn(5); + + // Available space (100 - 10 - 15 = 75) + when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(100); + when(mNotificationStackScrollLayoutController.getTopPadding()).thenReturn(10); + mNotificationPanelViewController.updateResources(); + + assertThat(mNotificationPanelViewController.getSpaceForLockscreenNotifications()) + .isEqualTo(75); + } + + @Test public void testSetPanelScrimMinFraction() { mNotificationPanelViewController.setPanelScrimMinFraction(0.5f); verify(mNotificationShadeDepthController).setPanelPullDownMinFraction(eq(0.5f)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt new file mode 100644 index 000000000000..39d33e86925e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt @@ -0,0 +1,118 @@ +package com.android.systemui.statusbar.phone.shade.transition + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.qs.QS +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.NotificationPanelViewController +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager +import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING +import com.android.systemui.statusbar.policy.FakeConfigurationController +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class ShadeTransitionControllerTest : SysuiTestCase() { + + @Mock private lateinit var npvc: NotificationPanelViewController + @Mock private lateinit var nsslController: NotificationStackScrollLayoutController + @Mock private lateinit var qs: QS + @Mock private lateinit var noOpOverScroller: NoOpOverScroller + @Mock private lateinit var splitShadeOverScroller: SplitShadeOverScroller + + private lateinit var controller: ShadeTransitionController + + private val configurationController = FakeConfigurationController() + private val panelExpansionStateManager = PanelExpansionStateManager() + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + controller = + ShadeTransitionController( + configurationController, + panelExpansionStateManager, + context, + splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller }, + noOpOverScroller) + + // Resetting as they are notified upon initialization. + reset(noOpOverScroller, splitShadeOverScroller) + } + + @Test + fun onPanelExpansionChanged_inSplitShade_forwardsToSplitShadeOverScroller() { + initLateProperties() + enableSplitShade() + + startPanelExpansion() + + verify(splitShadeOverScroller).onPanelStateChanged(STATE_OPENING) + verify(splitShadeOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT) + verifyZeroInteractions(noOpOverScroller) + } + + @Test + fun onPanelStateChanged_inSplitShade_propertiesNotInitialized_forwardsToNoOpOverScroller() { + enableSplitShade() + + startPanelExpansion() + + verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING) + verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT) + verifyZeroInteractions(splitShadeOverScroller) + } + + @Test + fun onPanelStateChanged_notInSplitShade_forwardsToNoOpOverScroller() { + initLateProperties() + disableSplitShade() + + startPanelExpansion() + + verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING) + verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT) + verifyZeroInteractions(splitShadeOverScroller) + } + + private fun initLateProperties() { + controller.qs = qs + controller.notificationStackScrollLayoutController = nsslController + controller.notificationPanelViewController = npvc + } + + private fun disableSplitShade() { + setSplitShadeEnabled(false) + } + + private fun enableSplitShade() { + setSplitShadeEnabled(true) + } + + private fun setSplitShadeEnabled(enabled: Boolean) { + overrideResource(R.bool.config_use_split_notification_shade, enabled) + configurationController.notifyConfigurationChanged() + } + + private fun startPanelExpansion() { + panelExpansionStateManager.onPanelExpansionChanged( + fraction = 0.5f, + expanded = true, + tracking = true, + dragDownPxAmount = DEFAULT_DRAG_DOWN_AMOUNT) + } + + companion object { + private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScrollerTest.kt new file mode 100644 index 000000000000..219737d1dfb4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScrollerTest.kt @@ -0,0 +1,107 @@ +package com.android.systemui.statusbar.phone.shade.transition + +import org.mockito.Mockito.`when` as whenever +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.qs.QS +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.ScrimController +import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED +import com.android.systemui.statusbar.phone.panelstate.STATE_OPEN +import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING +import com.android.systemui.statusbar.policy.FakeConfigurationController +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class SplitShadeOverScrollerTest : SysuiTestCase() { + + @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var scrimController: ScrimController + @Mock private lateinit var qs: QS + @Mock private lateinit var nsslController: NotificationStackScrollLayoutController + + private val configurationController = FakeConfigurationController() + private lateinit var overScroller: SplitShadeOverScroller + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + whenever(nsslController.height).thenReturn(1000) + overScroller = + SplitShadeOverScroller( + configurationController, dumpManager, context, scrimController, qs, nsslController) + } + + @Test + fun onDragDownAmountChanged_panelOpening_overScrolls_basedOnHeightAndMaxAmount() { + val maxOverScrollAmount = 50 + val dragDownAmount = 100f + overrideResource(R.dimen.shade_max_over_scroll_amount, maxOverScrollAmount) + configurationController.notifyConfigurationChanged() + + overScroller.onPanelStateChanged(STATE_OPENING) + overScroller.onDragDownAmountChanged(dragDownAmount) + + val expectedOverScrollAmount = + (dragDownAmount / nsslController.height * maxOverScrollAmount).toInt() + verify(qs).setOverScrollAmount(expectedOverScrollAmount) + verify(nsslController).setOverScrollAmount(expectedOverScrollAmount) + verify(scrimController).setNotificationsOverScrollAmount(expectedOverScrollAmount) + } + + @Test + fun onDragDownAmountChanged_panelClosed_doesNotOverScroll() { + overScroller.onPanelStateChanged(STATE_CLOSED) + overScroller.onDragDownAmountChanged(100f) + + verifyZeroInteractions(qs, scrimController, nsslController) + } + + @Test + fun onDragDownAmountChanged_panelOpen_doesNotOverScroll() { + overScroller.onPanelStateChanged(STATE_OPEN) + overScroller.onDragDownAmountChanged(100f) + + verifyZeroInteractions(qs, scrimController, nsslController) + } + + @Test + fun onPanelStateChanged_opening_thenOpen_releasesOverScroll() { + overScroller.onPanelStateChanged(STATE_OPENING) + overScroller.onDragDownAmountChanged(100f) + + overScroller.onPanelStateChanged(STATE_OPEN) + overScroller.finishAnimations() + + verify(qs, atLeastOnce()).setOverScrollAmount(0) + verify(scrimController, atLeastOnce()).setNotificationsOverScrollAmount(0) + verify(nsslController, atLeastOnce()).setOverScrollAmount(0) + } + + @Test + fun onPanelStateChanged_opening_thenClosed_releasesOverScroll() { + overScroller.onPanelStateChanged(STATE_OPENING) + overScroller.onDragDownAmountChanged(100f) + + overScroller.onPanelStateChanged(STATE_CLOSED) + overScroller.finishAnimations() + + verify(qs, atLeastOnce()).setOverScrollAmount(0) + verify(scrimController, atLeastOnce()).setNotificationsOverScrollAmount(0) + verify(nsslController, atLeastOnce()).setOverScrollAmount(0) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 3dfc94bcd5b6..c625dc7d4b50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -729,6 +729,18 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM, USER_SYSTEM); + reset(mResources); + when(mResources.getColor(eq(android.R.color.system_accent1_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getAccent1().get(6)); + when(mResources.getColor(eq(android.R.color.system_accent2_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getAccent2().get(6)); + when(mResources.getColor(eq(android.R.color.system_accent3_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getAccent3().get(6)); + when(mResources.getColor(eq(android.R.color.system_neutral1_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getNeutral1().get(6)); + when(mResources.getColor(eq(android.R.color.system_neutral2_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getNeutral2().get(6)); + // Defers event because we already have initial colors. verify(mThemeOverlayApplier, never()) .applyCurrentUserOverlays(any(), any(), anyInt(), any()); diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 5acae4859ee8..8fe57e18ea37 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -4414,7 +4414,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // a given package. Keep track of what we've done so far here; the list is // cleared at the start of every system restore pass, but preserved through // any install-time restore operations. - private final HashSet<String> mPrunedApps = new HashSet<>(); + private final SparseArray<Set<String>> mPrunedAppsPerUser = new SparseArray<>(); private final HashMap<Provider, ArrayList<RestoreUpdateRecord>> mUpdatesByProvider = new HashMap<>(); @@ -4537,7 +4537,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // We're starting a new "system" restore operation, so any widget restore // state that we see from here on is intended to replace the current // widget configuration of any/all of the affected apps. - mPrunedApps.clear(); + getPrunedAppsLocked(userId).clear(); mUpdatesByProvider.clear(); mUpdatesByHost.clear(); } @@ -4934,8 +4934,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // instances that are hosted by that app, and (b) all instances in other hosts // for which 'pkg' is the provider. We assume that we'll be restoring all of // these hosts & providers, so will be reconstructing a correct live state. + @GuardedBy("mLock") private void pruneWidgetStateLocked(String pkg, int userId) { - if (!mPrunedApps.contains(pkg)) { + final Set<String> prunedApps = getPrunedAppsLocked(userId); + if (!prunedApps.contains(pkg)) { if (DEBUG) { Slog.i(TAG, "pruning widget state for restoring package " + pkg); } @@ -4958,7 +4960,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku removeWidgetLocked(widget); } } - mPrunedApps.add(pkg); + prunedApps.add(pkg); } else { if (DEBUG) { Slog.i(TAG, "already pruned " + pkg + ", continuing normally"); @@ -4966,6 +4968,15 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } + @GuardedBy("mLock") + @NonNull + private Set<String> getPrunedAppsLocked(int userId) { + if (!mPrunedAppsPerUser.contains(userId)) { + mPrunedAppsPerUser.set(userId, new ArraySet<>()); + } + return mPrunedAppsPerUser.get(userId); + } + private boolean isProviderAndHostInUser(Widget widget, int userId) { // Backup only widgets hosted or provided by the owner profile. return widget.host.getUserId() == userId && (widget.provider == null diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index a67b858d7b63..1033aea4b09f 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -2803,6 +2803,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { + " callback.asBinder=" + callback.asBinder()); } + // In case this is triggered from the caller who has handled multiple SIM config change + // firstly, we need to update the status (mNumPhone and mCarrierPrivilegeStates) firstly. + // This is almost a no-op if there is no multiple SIM config change in advance. + onMultiSimConfigChanged(); + synchronized (mRecords) { if (!validatePhoneId(phoneId)) { throw new IllegalArgumentException("Invalid slot index: " + phoneId); @@ -2865,6 +2870,12 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { + ", <packages=" + pii(privilegedPackageNames) + ", uids=" + Arrays.toString(privilegedUids) + ">"); } + + // In case this is triggered from the caller who has handled multiple SIM config change + // firstly, we need to update the status (mNumPhone and mCarrierPrivilegeStates) firstly. + // This is almost a no-op if there is no multiple SIM config change in advance. + onMultiSimConfigChanged(); + synchronized (mRecords) { if (!validatePhoneId(phoneId)) { throw new IllegalArgumentException("Invalid slot index: " + phoneId); @@ -2900,6 +2911,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { + ", package=" + pii(packageName) + ", uid=" + uid); } + // In case this is triggered from the caller who has handled multiple SIM config change + // firstly, we need to update the status (mNumPhone and mCarrierServiceStates) firstly. + // This is almost a no-op if there is no multiple SIM config change in advance. + onMultiSimConfigChanged(); + synchronized (mRecords) { mCarrierServiceStates.set( phoneId, new Pair<>(packageName, uid)); @@ -3365,7 +3381,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } private boolean validatePhoneId(int phoneId) { - boolean valid = (phoneId >= 0) && (phoneId < mNumPhones); + // Call getActiveModemCount to get the latest value instead of depending on mNumPhone + boolean valid = (phoneId >= 0) && (phoneId < getTelephonyManager().getActiveModemCount()); if (VDBG) log("validatePhoneId: " + valid); return valid; } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 730907c7d049..235dbeeb9e27 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -21,7 +21,6 @@ import static android.Manifest.permission.REQUEST_COMPANION_START_FOREGROUND_SER import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND; import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND; import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; -import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -6527,10 +6526,14 @@ public final class ActiveServices { for (int con = 0; con < crs.size(); con++) { final ConnectionRecord cr = crs.get(con); final ProcessRecord clientPr = cr.binding.client; - // Persistent process does not propagate BG-FGS-start capability - // down to service over binding. - if (clientPr.mState.getCurProcState() - <= PROCESS_STATE_PERSISTENT_UI) { + // If a binding is from a persistent process, we don't automatically + // always allow the bindee to allow FGS BG starts. In this case, + // the binder will have to explicitly make sure the bindee's + // procstate will be BFGS or above. Otherwise, for example, even if + // the system server binds to an app with BIND_NOT_FOREGROUND, + // the binder would have to be able to start FGS, which is not what + // we want. (e.g. job services shouldn't be allowed BG-FGS.) + if (clientPr.isPersistent()) { continue; } final int clientPid = clientPr.mPid; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c5a4ca4dc0e5..91f6eeb875f6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1024,30 +1024,9 @@ public class ActivityManagerService extends IActivityManager.Stub private final ActivityMetricsLaunchObserver mActivityLaunchObserver = new ActivityMetricsLaunchObserver() { @Override - public void onActivityLaunched(byte[] activity, int temperature) { + public void onActivityLaunched(long id, ComponentName name, int temperature) { mAppProfiler.onActivityLaunched(); } - - // The other observer methods are unused - @Override - public void onIntentStarted(Intent intent, long timestampNs) { - } - - @Override - public void onIntentFailed() { - } - - @Override - public void onActivityLaunchCancelled(byte[] abortingActivity) { - } - - @Override - public void onActivityLaunchFinished(byte[] finalActivity, long timestampNs) { - } - - @Override - public void onReportFullyDrawn(byte[] finalActivity, long timestampNs) { - } }; private volatile boolean mBinderTransactionTrackingEnabled = false; diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 71ae92aecbcf..3e5786eaa333 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -1200,8 +1200,19 @@ final class ActivityManagerShellCommand extends ShellCommand { } catch (NumberFormatException e) { packageName = arg; } - mInterface.crashApplicationWithType(-1, pid, packageName, userId, "shell-induced crash", - false, CrashedByAdbException.TYPE_ID); + + int[] userIds = (userId == UserHandle.USER_ALL) ? mInternal.mUserController.getUserIds() + : new int[]{userId}; + for (int id : userIds) { + if (mInternal.mUserController.hasUserRestriction( + UserManager.DISALLOW_DEBUGGING_FEATURES, id)) { + getOutPrintWriter().println( + "Shell does not have permission to crash packages for user " + id); + continue; + } + mInterface.crashApplicationWithType(-1, pid, packageName, id, "shell-induced crash", + false, CrashedByAdbException.TYPE_ID); + } return 0; } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 36c40a1b97dc..ed492bc7344c 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -33,6 +33,7 @@ import android.app.ActivityOptions; import android.app.AnrController; import android.app.ApplicationErrorReport; import android.app.ApplicationExitInfo; +import android.app.RemoteServiceException.CrashedByAdbException; import android.app.usage.UsageStatsManager; import android.content.ActivityNotFoundException; import android.content.Context; @@ -523,6 +524,16 @@ class AppErrors { return; } + if (exceptionTypeId == CrashedByAdbException.TYPE_ID) { + String[] packages = proc.getPackageList(); + for (int i = 0; i < packages.length; i++) { + if (mService.mPackageManagerInt.isPackageStateProtected(packages[i], proc.userId)) { + Slog.w(TAG, "crashApplication: Can not crash protected package " + packages[i]); + return; + } + } + } + proc.scheduleCrashLocked(message, exceptionTypeId, extras); if (force) { // If the app is responsive, the scheduled crash will happen as expected diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java index 672736df88f3..18fb6a480f29 100644 --- a/services/core/java/com/android/server/am/DropboxRateLimiter.java +++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java @@ -64,9 +64,10 @@ public class DropboxRateLimiter { } if (now - errRecord.getStartTime() > RATE_LIMIT_BUFFER_DURATION) { + final int errCount = recentlyDroppedCount(errRecord); errRecord.setStartTime(now); errRecord.setCount(1); - return new RateLimitResult(false, recentlyDroppedCount(errRecord)); + return new RateLimitResult(false, errCount); } errRecord.incrementCount(); diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 19a93f30937f..63609f77dc75 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -46,6 +46,7 @@ import java.util.Date; import java.util.Deque; import java.util.List; import java.util.Locale; +import java.util.function.Consumer; /** * A scheduler for biometric HAL operations. Maintains a queue of {@link BaseClientMonitor} @@ -457,22 +458,33 @@ public class BiometricScheduler { } /** + * Get current operation <code>BaseClientMonitor</code> + * @deprecated TODO: b/229994966, encapsulate client monitors * @return the current operation */ + @Deprecated + @Nullable public BaseClientMonitor getCurrentClient() { return mCurrentOperation != null ? mCurrentOperation.getClientMonitor() : null; } - /** The current operation if the requestId is set and matches. */ + /** + * The current operation if the requestId is set and matches. + * @deprecated TODO: b/229994966, encapsulate client monitors + */ @Deprecated @Nullable - public BaseClientMonitor getCurrentClientIfMatches(long requestId) { - if (mCurrentOperation != null) { - if (mCurrentOperation.isMatchingRequestId(requestId)) { - return mCurrentOperation.getClientMonitor(); + public void getCurrentClientIfMatches(long requestId, + @NonNull Consumer<BaseClientMonitor> clientMonitorConsumer) { + mHandler.post(() -> { + if (mCurrentOperation != null) { + if (mCurrentOperation.isMatchingRequestId(requestId)) { + clientMonitorConsumer.accept(mCurrentOperation.getClientMonitor()); + return; + } } - } - return null; + clientMonitorConsumer.accept(null); + }); } public int getCurrentPendingCount() { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 7d5b77c2d711..998a8e1e9f90 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -582,35 +582,35 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @Override public void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major) { - final BaseClientMonitor client = - mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId); - if (!(client instanceof Udfps)) { - Slog.e(getTag(), "onPointerDown received during client: " + client); - return; - } - ((Udfps) client).onPointerDown(x, y, minor, major); + mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> { + if (!(client instanceof Udfps)) { + Slog.e(getTag(), "onPointerDown received during client: " + client); + return; + } + ((Udfps) client).onPointerDown(x, y, minor, major); + }); } @Override public void onPointerUp(long requestId, int sensorId) { - final BaseClientMonitor client = - mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId); - if (!(client instanceof Udfps)) { - Slog.e(getTag(), "onPointerUp received during client: " + client); - return; - } - ((Udfps) client).onPointerUp(); + mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> { + if (!(client instanceof Udfps)) { + Slog.e(getTag(), "onPointerUp received during client: " + client); + return; + } + ((Udfps) client).onPointerUp(); + }); } @Override public void onUiReady(long requestId, int sensorId) { - final BaseClientMonitor client = - mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId); - if (!(client instanceof Udfps)) { - Slog.e(getTag(), "onUiReady received during client: " + client); - return; - } - ((Udfps) client).onUiReady(); + mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> { + if (!(client instanceof Udfps)) { + Slog.e(getTag(), "onUiReady received during client: " + client); + return; + } + ((Udfps) client).onUiReady(); + }); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 52dbe2460e1c..78a30e820c80 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -794,32 +794,35 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @Override public void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major) { - final BaseClientMonitor client = mScheduler.getCurrentClientIfMatches(requestId); - if (!(client instanceof Udfps)) { - Slog.w(TAG, "onFingerDown received during client: " + client); - return; - } - ((Udfps) client).onPointerDown(x, y, minor, major); + mScheduler.getCurrentClientIfMatches(requestId, (client) -> { + if (!(client instanceof Udfps)) { + Slog.w(TAG, "onFingerDown received during client: " + client); + return; + } + ((Udfps) client).onPointerDown(x, y, minor, major); + }); } @Override public void onPointerUp(long requestId, int sensorId) { - final BaseClientMonitor client = mScheduler.getCurrentClientIfMatches(requestId); - if (!(client instanceof Udfps)) { - Slog.w(TAG, "onFingerDown received during client: " + client); - return; - } - ((Udfps) client).onPointerUp(); + mScheduler.getCurrentClientIfMatches(requestId, (client) -> { + if (!(client instanceof Udfps)) { + Slog.w(TAG, "onFingerDown received during client: " + client); + return; + } + ((Udfps) client).onPointerUp(); + }); } @Override public void onUiReady(long requestId, int sensorId) { - final BaseClientMonitor client = mScheduler.getCurrentClientIfMatches(requestId); - if (!(client instanceof Udfps)) { - Slog.w(TAG, "onUiReady received during client: " + client); - return; - } - ((Udfps) client).onUiReady(); + mScheduler.getCurrentClientIfMatches(requestId, (client) -> { + if (!(client instanceof Udfps)) { + Slog.w(TAG, "onUiReady received during client: " + client); + return; + } + ((Udfps) client).onUiReady(); + }); } @Override diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index f68d22acd6ab..4e1d899b26a6 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -17,13 +17,21 @@ package com.android.server.dreams; import static android.Manifest.permission.BIND_DREAM_SERVICE; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID; + +import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.TaskInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ServiceInfo; @@ -54,6 +62,7 @@ import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.wm.ActivityInterceptorCallback; import com.android.server.wm.ActivityTaskManagerInternal; import java.io.FileDescriptor; @@ -83,6 +92,7 @@ public final class DreamManagerService extends SystemService { private final UiEventLogger mUiEventLogger; private final DreamUiEventLogger mDreamUiEventLogger; private final ComponentName mAmbientDisplayComponent; + private final boolean mDismissDreamOnActivityStart; private Binder mCurrentDreamToken; private ComponentName mCurrentDreamName; @@ -99,6 +109,26 @@ public final class DreamManagerService extends SystemService { private ComponentName mDreamOverlayServiceName; private AmbientDisplayConfiguration mDozeConfig; + private final ActivityInterceptorCallback mActivityInterceptorCallback = + new ActivityInterceptorCallback() { + @Nullable + @Override + public ActivityInterceptResult intercept(ActivityInterceptorInfo info) { + return null; + } + + @Override + public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo, + ActivityInterceptorInfo info) { + final int activityType = taskInfo.getActivityType(); + final boolean activityAllowed = activityType == ACTIVITY_TYPE_HOME + || activityType == ACTIVITY_TYPE_DREAM + || activityType == ACTIVITY_TYPE_ASSISTANT; + if (mCurrentDreamToken != null && !mCurrentDreamIsWaking && !activityAllowed) { + stopDreamInternal(false, "activity starting: " + activityInfo.name); + } + } + }; public DreamManagerService(Context context) { super(context); @@ -118,6 +148,8 @@ public final class DreamManagerService extends SystemService { mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent()); mDreamsOnlyEnabledForSystemUser = mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForSystemUser); + mDismissDreamOnActivityStart = mContext.getResources().getBoolean( + R.bool.config_dismissDreamOnActivityStart); } @Override @@ -145,6 +177,12 @@ public final class DreamManagerService extends SystemService { Settings.Secure.getUriFor(Settings.Secure.DOZE_DOUBLE_TAP_GESTURE), false, mDozeEnabledObserver, UserHandle.USER_ALL); writePulseGestureEnabled(); + + if (mDismissDreamOnActivityStart) { + mAtmInternal.registerActivityStartInterceptor( + DREAM_MANAGER_ORDERED_ID, + mActivityInterceptorCallback); + } } } diff --git a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java index db2cb52d778e..5253d34a38f0 100644 --- a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java +++ b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java @@ -20,7 +20,11 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.UserIdInt; +import android.app.AppGlobals; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -34,7 +38,9 @@ import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; /** * Gets the service name using a framework resources, temporarily changing the service if necessary @@ -49,10 +55,14 @@ public final class FrameworkResourcesServiceNameResolver implements ServiceNameR /** Handler message to {@link #resetTemporaryService(int)} */ private static final int MSG_RESET_TEMPORARY_SERVICE = 0; - @NonNull private final Context mContext; - @NonNull private final Object mLock = new Object(); - @StringRes private final int mStringResourceId; - @ArrayRes private final int mArrayResourceId; + @NonNull + private final Context mContext; + @NonNull + private final Object mLock = new Object(); + @StringRes + private final int mStringResourceId; + @ArrayRes + private final int mArrayResourceId; private final boolean mIsMultiple; /** * Map of temporary service name list set by {@link #setTemporaryServices(int, String[], int)}, @@ -71,7 +81,8 @@ public final class FrameworkResourcesServiceNameResolver implements ServiceNameR */ @GuardedBy("mLock") private final SparseBooleanArray mDefaultServicesDisabled = new SparseBooleanArray(); - @Nullable private NameResolverListener mOnSetCallback; + @Nullable + private NameResolverListener mOnSetCallback; /** * When the temporary service will expire (and reset back to the default). */ @@ -160,10 +171,33 @@ public final class FrameworkResourcesServiceNameResolver implements ServiceNameR public String[] getDefaultServiceNameList(int userId) { synchronized (mLock) { if (mIsMultiple) { - return mContext.getResources().getStringArray(mArrayResourceId); + String[] serviceNameList = mContext.getResources().getStringArray(mArrayResourceId); + // Filter out unimplemented services + // Initialize the validated array as null because we do not know the final size. + List<String> validatedServiceNameList = new ArrayList<>(); + try { + for (int i = 0; i < serviceNameList.length; i++) { + if (TextUtils.isEmpty(serviceNameList[i])) { + continue; + } + ComponentName serviceComponent = ComponentName.unflattenFromString( + serviceNameList[i]); + ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo( + serviceComponent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); + if (serviceInfo != null) { + validatedServiceNameList.add(serviceNameList[i]); + } + } + } catch (Exception e) { + Slog.e(TAG, "Could not validate provided services.", e); + } + String[] validatedServiceNameArray = new String[validatedServiceNameList.size()]; + return validatedServiceNameList.toArray(validatedServiceNameArray); } else { final String name = mContext.getString(mStringResourceId); - return TextUtils.isEmpty(name) ? new String[0] : new String[] { name }; + return TextUtils.isEmpty(name) ? new String[0] : new String[]{name}; } } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index b978131e175c..57d89dae588e 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -133,10 +133,13 @@ public abstract class InputMethodManagerInternal { * * @param windowToken the window token that is now in control, or {@code null} if no client * window is in control of the IME. - * @param imeParentChanged {@code true} when the window manager thoughts the IME surface parent - * will end up to change later, or {@code false} otherwise. */ - public abstract void reportImeControl(@Nullable IBinder windowToken, boolean imeParentChanged); + public abstract void reportImeControl(@Nullable IBinder windowToken); + + /** + * Indicates that the IME window has re-parented to the new target when the IME control changed. + */ + public abstract void onImeParentChanged(); /** * Destroys the IME surface. @@ -226,8 +229,11 @@ public abstract class InputMethodManagerInternal { } @Override - public void reportImeControl(@Nullable IBinder windowToken, - boolean imeParentChanged) { + public void reportImeControl(@Nullable IBinder windowToken) { + } + + @Override + public void onImeParentChanged() { } @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index ce8b9fabd5a0..6af00b3fbeea 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -5700,19 +5700,23 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void reportImeControl(@Nullable IBinder windowToken, boolean imeParentChanged) { + public void reportImeControl(@Nullable IBinder windowToken) { synchronized (ImfLock.class) { if (mCurFocusedWindow != windowToken) { // mCurPerceptible was set by the focused window, but it is no longer in // control, so we reset mCurPerceptible. mCurPerceptible = true; } - if (imeParentChanged) { - // Hide the IME method menu earlier when the IME surface parent will change in - // case seeing the dialog dismiss flickering during the next focused window - // starting the input connection. - mMenuController.hideInputMethodMenu(); - } + } + } + + @Override + public void onImeParentChanged() { + synchronized (ImfLock.class) { + // Hide the IME method menu when the IME surface parent will change in + // case seeing the dialog dismiss flickering during the next focused window + // starting the input connection. + mMenuController.hideInputMethodMenu(); } } diff --git a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java index 70a222fb09c5..dc5299077cc9 100644 --- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java +++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java @@ -34,6 +34,8 @@ import java.util.Objects; public class SystemEmergencyHelper extends EmergencyHelper { private final Context mContext; + private final EmergencyCallTelephonyCallback mEmergencyCallTelephonyCallback = + new EmergencyCallTelephonyCallback(); TelephonyManager mTelephonyManager; @@ -56,7 +58,7 @@ public class SystemEmergencyHelper extends EmergencyHelper { // TODO: this doesn't account for multisim phones mTelephonyManager.registerTelephonyCallback(FgThread.getExecutor(), - new EmergencyCallTelephonyCallback()); + mEmergencyCallTelephonyCallback); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { diff --git a/services/core/java/com/android/server/media/MediaShellCommand.java b/services/core/java/com/android/server/media/MediaShellCommand.java index d175d87651de..a56380827f2c 100644 --- a/services/core/java/com/android/server/media/MediaShellCommand.java +++ b/services/core/java/com/android/server/media/MediaShellCommand.java @@ -107,7 +107,6 @@ public class MediaShellCommand extends ShellCommand { public void onHelp() { mWriter.println("usage: media_session [subcommand] [options]"); mWriter.println(" media_session dispatch KEY"); - mWriter.println(" media_session dispatch KEY"); mWriter.println(" media_session list-sessions"); mWriter.println(" media_session monitor <tag>"); mWriter.println(" media_session volume [options]"); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 2d8d4f588192..57a1fe04b690 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -4187,8 +4187,8 @@ final class InstallPackageHelper { assertOverlayIsValid(pkg, parseFlags, scanFlags); } - // If the package is not on a system partition ensure it is signed with at least the - // minimum signature scheme version required for its target SDK. + // Ensure the package is signed with at least the minimum signature scheme version + // required for its target SDK. ScanPackageUtils.assertMinSignatureSchemeIsValid(pkg, parseFlags); } } diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 4e8313bf1891..0dc188b75d5e 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -690,16 +690,14 @@ final class ScanPackageUtils { public static void assertMinSignatureSchemeIsValid(AndroidPackage pkg, @ParsingPackageUtils.ParseFlags int parseFlags) throws PackageManagerException { - if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) { - int minSignatureSchemeVersion = - ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk( - pkg.getTargetSdkVersion()); - if (pkg.getSigningDetails().getSignatureSchemeVersion() - < minSignatureSchemeVersion) { - throw new PackageManagerException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "No signature found in package of version " + minSignatureSchemeVersion - + " or newer for package " + pkg.getPackageName()); - } + int minSignatureSchemeVersion = + ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk( + pkg.getTargetSdkVersion()); + if (pkg.getSigningDetails().getSignatureSchemeVersion() + < minSignatureSchemeVersion) { + throw new PackageManagerException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No signature found in package of version " + minSignatureSchemeVersion + + " or newer for package " + pkg.getPackageName()); } } diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java index 2960bc9a3790..c0c234953297 100644 --- a/services/core/java/com/android/server/pm/ShortcutLauncher.java +++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java @@ -420,4 +420,14 @@ class ShortcutLauncher extends ShortcutPackageItem { ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) { return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName))); } + + @Override + protected File getShortcutPackageItemFile() { + final File path = new File(mShortcutUser.mService.injectUserDataPath( + mShortcutUser.getUserId()), ShortcutUser.DIRECTORY_LUANCHERS); + // Package user id and owner id can have different values for ShortcutLaunchers. Adding + // user Id to the file name to create a unique path. Owner id is used in the root path. + final String fileName = getPackageName() + getPackageUserId() + ".xml"; + return new File(path, fileName); + } } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index f57eaaef25a4..fef6ce1f67b3 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -160,8 +160,6 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String KEY_BITMAPS = "bitmaps"; private static final String KEY_BITMAP_BYTES = "bitmapBytes"; - private final Object mLock = new Object(); - private final Executor mExecutor; /** @@ -779,7 +777,7 @@ class ShortcutPackage extends ShortcutPackageItem { return false; } mApiCallCount++; - s.scheduleSaveUser(getOwnerUserId()); + scheduleSave(); return true; } @@ -789,7 +787,7 @@ class ShortcutPackage extends ShortcutPackageItem { } if (mApiCallCount > 0) { mApiCallCount = 0; - mShortcutUser.mService.scheduleSaveUser(getOwnerUserId()); + scheduleSave(); } } @@ -1890,15 +1888,12 @@ class ShortcutPackage extends ShortcutPackageItem { final ShortcutPackage ret = new ShortcutPackage(shortcutUser, shortcutUser.getUserId(), packageName); - synchronized (ret.mLock) { ret.mIsAppSearchSchemaUpToDate = ShortcutService.parseIntAttribute( parser, ATTR_SCHEMA_VERSON, 0) == AppSearchShortcutInfo.SCHEMA_VERSION; } - ret.mApiCallCount = - ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT); - ret.mLastResetTime = - ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET); + ret.mApiCallCount = ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT); + ret.mLastResetTime = ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET); final int outerDepth = parser.getDepth(); @@ -2440,16 +2435,15 @@ class ShortcutPackage extends ShortcutPackageItem { }))); } - void persistsAllShortcutsAsync() { - synchronized (mLock) { - final Map<String, ShortcutInfo> copy = mShortcuts; - if (!mTransientShortcuts.isEmpty()) { - copy.putAll(mTransientShortcuts); - mTransientShortcuts.clear(); - } - saveShortcutsAsync(copy.values().stream().filter(ShortcutInfo::usesQuota).collect( - Collectors.toList())); + @Override + void scheduleSaveToAppSearchLocked() { + final Map<String, ShortcutInfo> copy = new ArrayMap<>(mShortcuts); + if (!mTransientShortcuts.isEmpty()) { + copy.putAll(mTransientShortcuts); + mTransientShortcuts.clear(); } + saveShortcutsAsync(copy.values().stream().filter(ShortcutInfo::usesQuota).collect( + Collectors.toList())); } private void saveShortcutsAsync( @@ -2548,4 +2542,12 @@ class ShortcutPackage extends ShortcutPackageItem { Binder.restoreCallingIdentity(callingIdentity); } } + + @Override + protected File getShortcutPackageItemFile() { + final File path = new File(mShortcutUser.mService.injectUserDataPath( + mShortcutUser.getUserId()), ShortcutUser.DIRECTORY_PACKAGES); + final String fileName = getPackageName() + ".xml"; + return new File(path, fileName); + } } diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java index 829133c9854a..6e0436f208e3 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java +++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java @@ -23,6 +23,7 @@ import android.util.Slog; import android.util.TypedXmlSerializer; import android.util.Xml; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import org.json.JSONException; @@ -36,7 +37,7 @@ import java.nio.charset.StandardCharsets; import java.util.Objects; /** - * All methods should be guarded by {@code #mShortcutUser.mService.mLock}. + * All methods should be either guarded by {@code #mShortcutUser.mService.mLock} or {@code #mLock}. */ abstract class ShortcutPackageItem { private static final String TAG = ShortcutService.TAG; @@ -49,6 +50,8 @@ abstract class ShortcutPackageItem { protected ShortcutUser mShortcutUser; + protected final Object mLock = new Object(); + protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser, int packageUserId, @NonNull String packageName, @NonNull ShortcutPackageInfo packageInfo) { @@ -98,7 +101,7 @@ abstract class ShortcutPackageItem { } final ShortcutService s = mShortcutUser.mService; mPackageInfo.refreshSignature(s, this); - s.scheduleSaveUser(getOwnerUserId()); + scheduleSave(); } public void attemptToRestoreIfNeededAndSave() { @@ -138,7 +141,7 @@ abstract class ShortcutPackageItem { // Either way, it's no longer a shadow. mPackageInfo.setShadow(false); - s.scheduleSaveUser(mPackageUserId); + scheduleSave(); } protected abstract boolean canRestoreAnyVersion(); @@ -148,7 +151,8 @@ abstract class ShortcutPackageItem { public abstract void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup) throws IOException, XmlPullParserException; - public void saveToFile(File path, boolean forBackup) { + @GuardedBy("mLock") + public void saveToFileLocked(File path, boolean forBackup) { final AtomicFile file = new AtomicFile(path); FileOutputStream os = null; try { @@ -176,6 +180,11 @@ abstract class ShortcutPackageItem { } } + @GuardedBy("mLock") + void scheduleSaveToAppSearchLocked() { + + } + public JSONObject dumpCheckin(boolean clear) throws JSONException { final JSONObject result = new JSONObject(); result.put(KEY_NAME, mPackageName); @@ -187,4 +196,36 @@ abstract class ShortcutPackageItem { */ public void verifyStates() { } + + public void scheduleSave() { + mShortcutUser.mService.injectPostToHandlerDebounced( + mSaveShortcutPackageRunner, mSaveShortcutPackageRunner); + } + + private final Runnable mSaveShortcutPackageRunner = this::saveShortcutPackageItem; + + void saveShortcutPackageItem() { + // Wait for bitmap saves to conclude before proceeding to saving shortcuts. + mShortcutUser.mService.waitForBitmapSaves(); + // Save each ShortcutPackageItem in a separate Xml file. + final File path = getShortcutPackageItemFile(); + if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) { + Slog.d(TAG, "Saving package item " + getPackageName() + " to " + path); + } + synchronized (mLock) { + path.getParentFile().mkdirs(); + // TODO: Since we are persisting shortcuts into AppSearch, we should read from/write to + // AppSearch as opposed to maintaining a separate XML file. + saveToFileLocked(path, false /*forBackup*/); + scheduleSaveToAppSearchLocked(); + } + } + + void removeShortcutPackageItem() { + synchronized (mLock) { + getShortcutPackageItemFile().delete(); + } + } + + protected abstract File getShortcutPackageItemFile(); } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 9627c4394db7..780f976d2a40 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -748,7 +748,7 @@ public class ShortcutService extends IShortcutService.Stub { getUserShortcutsLocked(userId).cancelAllInFlightTasks(); // Save all dirty information. - saveDirtyInfo(false); + saveDirtyInfo(); // Unload mUsers.delete(userId); @@ -1203,10 +1203,6 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting void saveDirtyInfo() { - saveDirtyInfo(true); - } - - private void saveDirtyInfo(boolean saveShortcutsInAppSearch) { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "saveDirtyInfo"); } @@ -1221,10 +1217,6 @@ public class ShortcutService extends IShortcutService.Stub { if (userId == UserHandle.USER_NULL) { // USER_NULL for base state. saveBaseStateLocked(); } else { - if (saveShortcutsInAppSearch) { - getUserShortcutsLocked(userId).forAllPackages( - ShortcutPackage::persistsAllShortcutsAsync); - } saveUserLocked(userId); } } @@ -1816,7 +1808,7 @@ public class ShortcutService extends IShortcutService.Stub { } injectPostToHandlerDebounced(sp, notifyListenerRunnable(packageName, userId)); notifyShortcutChangeCallbacks(packageName, userId, changedShortcuts, removedShortcuts); - scheduleSaveUser(userId); + sp.scheduleSave(); } private void notifyListeners(@NonNull final String packageName, @UserIdInt final int userId) { @@ -2878,12 +2870,11 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutUser user = getUserShortcutsLocked(owningUserId); boolean doNotify = false; - // First, remove the package from the package list (if the package is a publisher). - if (packageUserId == owningUserId) { - if (user.removePackage(packageName) != null) { - doNotify = true; - } + final ShortcutPackage sp = (packageUserId == owningUserId) + ? user.removePackage(packageName) : null; + if (sp != null) { + doNotify = true; } // Also remove from the launcher list (if the package is a launcher). @@ -2906,6 +2897,10 @@ public class ShortcutService extends IShortcutService.Stub { // notifyListeners. user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); } + if (!appStillExists && (packageUserId == owningUserId) && sp != null) { + // If the app is removed altogether, we can get rid of the xml as well + injectPostToHandler(() -> sp.removeShortcutPackageItem()); + } if (!wasUserLoaded) { // Note this will execute the scheduled save. @@ -3788,7 +3783,7 @@ public class ShortcutService extends IShortcutService.Stub { if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) { mHandler.removeCallbacks(mSaveDirtyInfoRunner); forEachLoadedUserLocked(ShortcutUser::cancelAllInFlightTasks); - saveDirtyInfo(false); + saveDirtyInfo(); } mShutdown.set(true); } @@ -4457,7 +4452,7 @@ public class ShortcutService extends IShortcutService.Stub { // Save to the filesystem. scheduleSaveUser(userId); - saveDirtyInfo(false); + saveDirtyInfo(); // Note, in case of backup, we don't have to wait on bitmap saving, because we don't // back up bitmaps anyway. @@ -5352,8 +5347,7 @@ public class ShortcutService extends IShortcutService.Stub { } } - @VisibleForTesting - void waitForBitmapSavesForTest() { + void waitForBitmapSaves() { synchronized (mLock) { mShortcutBitmapSaver.waitForAllSavesLocked(); } diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index 4bb5dcfa4b26..75e18b547c55 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -407,35 +407,10 @@ class ShortcutUser { } spi.saveToXml(out, forBackup); } else { - // Save each ShortcutPackageItem in a separate Xml file. - final File path = getShortcutPackageItemFile(spi); - if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) { - Slog.d(TAG, "Saving package item " + spi.getPackageName() + " to " + path); - } - - path.getParentFile().mkdirs(); - spi.saveToFile(path, forBackup); + spi.saveShortcutPackageItem(); } } - private File getShortcutPackageItemFile(ShortcutPackageItem spi) { - boolean isShortcutLauncher = spi instanceof ShortcutLauncher; - - final File path = new File(mService.injectUserDataPath(mUserId), - isShortcutLauncher ? DIRECTORY_LUANCHERS : DIRECTORY_PACKAGES); - - final String fileName; - if (isShortcutLauncher) { - // Package user id and owner id can have different values for ShortcutLaunchers. Adding - // user Id to the file name to create a unique path. Owner id is used in the root path. - fileName = spi.getPackageName() + spi.getPackageUserId() + ".xml"; - } else { - fileName = spi.getPackageName() + ".xml"; - } - - return new File(path, fileName); - } - public static ShortcutUser loadFromXml(ShortcutService s, TypedXmlPullParser parser, int userId, boolean fromBackup) throws IOException, XmlPullParserException, InvalidFileFormatException { final ShortcutUser ret = new ShortcutUser(s, userId); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 358e71a70550..0dabff8370ba 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -4291,7 +4291,7 @@ public class UserManagerService extends IUserManager.Stub { for (int i = 0; i < userSize; i++) { final UserData user = mUsers.valueAt(i); if (DBG) Slog.d(LOG_TAG, i + ":" + user.info.toFullString()); - if (user.info.preCreated && user.info.userType.equals(userType)) { + if (user.info.preCreated && !user.info.partial && user.info.userType.equals(userType)) { if (!user.info.isInitialized()) { Slog.w(LOG_TAG, "found pre-created user of type " + userType + ", but it's not initialized yet: " + user.info.toFullString()); diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 284c29ecfbe9..a04f6d64ef8f 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -904,14 +904,6 @@ final class DefaultPermissionGrantPolicy { COARSE_BACKGROUND_LOCATION_PERMISSIONS, CONTACTS_PERMISSIONS); } - // Attention Service - String attentionServicePackageName = - mContext.getPackageManager().getAttentionServicePackageName(); - if (!TextUtils.isEmpty(attentionServicePackageName)) { - grantPermissionsToSystemPackage(pm, attentionServicePackageName, userId, - CAMERA_PERMISSIONS); - } - // There is no real "marker" interface to identify the shared storage backup, it is // hardcoded in BackupManagerService.SHARED_BACKUP_AGENT_PACKAGE. grantSystemFixedPermissionsToSystemPackage(pm, "com.android.sharedstoragebackup", userId, diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index 9897c42e4cec..e1ff9ead6740 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -3105,11 +3105,9 @@ public class ParsingPackageUtils { } final ParseResult<SigningDetails> verified; if (skipVerify) { - // systemDir APKs are already trusted, save time by not verifying; since the - // signature is not verified and some system apps can have their V2+ signatures - // stripped allow pulling the certs from the jar signature. + // systemDir APKs are already trusted, save time by not verifying verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification(input, baseCodePath, - SigningDetails.SignatureSchemeVersion.JAR); + minSignatureScheme); } else { verified = ApkSignatureVerifier.verify(input, baseCodePath, minSignatureScheme); } diff --git a/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING index ba4a62cdbbf1..8a1982a339ea 100644 --- a/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING @@ -12,9 +12,6 @@ "name": "CtsDomainVerificationDeviceStandaloneTestCases" }, { - "name": "CtsDomainVerificationDeviceMultiUserTestCases" - }, - { "name": "CtsDomainVerificationHostTestCases" } ] diff --git a/services/core/java/com/android/server/policy/PermissionPolicyInternal.java b/services/core/java/com/android/server/policy/PermissionPolicyInternal.java index 92b9944b74cf..77885c7ab8ba 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyInternal.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyInternal.java @@ -74,10 +74,13 @@ public abstract class PermissionPolicyInternal { * * @param taskInfo The task to be checked * @param currPkg The package of the current top visible activity + * @param callingPkg The package that started the top visible activity * @param intent The intent of the current top visible activity + * @param activityName The name of the current top visible activity */ public abstract boolean shouldShowNotificationDialogForTask(@Nullable TaskInfo taskInfo, - @Nullable String currPkg, @Nullable Intent intent); + @Nullable String currPkg, @Nullable String callingPkg, @Nullable Intent intent, + @NonNull String activityName); /** * @return true if an intent will resolve to a permission request dialog activity diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 14abc9aabc29..7ba1cadc5c8b 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -35,6 +35,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.AppOpsManager; @@ -1166,7 +1167,8 @@ public final class PermissionPolicyService extends SystemService { ActivityInterceptorInfo info) { super.onActivityLaunched(taskInfo, activityInfo, info); if (!shouldShowNotificationDialogOrClearFlags(taskInfo, - activityInfo.packageName, info.intent, info.checkedOptions, true) + activityInfo.packageName, info.callingPackage, info.intent, + info.checkedOptions, activityInfo.name, true) || isNoDisplayActivity(activityInfo)) { return; } @@ -1237,9 +1239,9 @@ public final class PermissionPolicyService extends SystemService { @Override public boolean shouldShowNotificationDialogForTask(TaskInfo taskInfo, String currPkg, - Intent intent) { - return shouldShowNotificationDialogOrClearFlags( - taskInfo, currPkg, intent, null, false); + String callingPkg, Intent intent, String activityName) { + return shouldShowNotificationDialogOrClearFlags(taskInfo, currPkg, callingPkg, intent, + null, activityName, false); } private boolean isNoDisplayActivity(@NonNull ActivityInfo aInfo) { @@ -1265,23 +1267,61 @@ public final class PermissionPolicyService extends SystemService { * 1. The isEligibleForLegacyPermissionPrompt ActivityOption is set, or * 2. The intent is a launcher intent (action is ACTION_MAIN, category is LAUNCHER), or * 3. The activity belongs to the same package as the one which launched the task - * originally, and the task was started with a launcher intent + * originally, and the task was started with a launcher intent, or + * 4. The activity is the first activity in a new task, and was started by the app the + * activity belongs to, and that app has another task that is currently focused, which was + * started with a launcher intent. This case seeks to identify cases where an app launches, + * then immediately trampolines to a new activity and task. * @param taskInfo The task to be checked * @param currPkg The package of the current top visible activity + * @param callingPkg The package that initiated this dialog action * @param intent The intent of the current top visible activity + * @param options The ActivityOptions of the newly started activity, if this is called due + * to an activity start + * @param startedActivity The ActivityInfo of the newly started activity, if this is called + * due to an activity start */ private boolean shouldShowNotificationDialogOrClearFlags(TaskInfo taskInfo, String currPkg, - Intent intent, ActivityOptions options, boolean activityStart) { - if (intent == null || currPkg == null || taskInfo == null + String callingPkg, Intent intent, ActivityOptions options, + String topActivityName, boolean startedActivity) { + if (intent == null || currPkg == null || taskInfo == null || topActivityName == null || (!(taskInfo.isFocused && taskInfo.isVisible && taskInfo.isRunning) - && !activityStart)) { + && !startedActivity)) { return false; } - return isLauncherIntent(intent) || (options != null && options.isEligibleForLegacyPermissionPrompt()) - || (currPkg.equals(taskInfo.baseActivity.getPackageName()) - && isLauncherIntent(taskInfo.baseIntent)); + || isTaskStartedFromLauncher(currPkg, taskInfo) + || (isTaskPotentialTrampoline(topActivityName, currPkg, callingPkg, taskInfo, + intent) + && (!startedActivity || pkgHasRunningLauncherTask(currPkg, taskInfo))); + } + + private boolean isTaskPotentialTrampoline(String activityName, String currPkg, + String callingPkg, TaskInfo taskInfo, Intent intent) { + return currPkg.equals(callingPkg) && taskInfo.baseIntent.filterEquals(intent) + && taskInfo.numActivities == 1 + && activityName.equals(taskInfo.topActivityInfo.name); + } + + private boolean pkgHasRunningLauncherTask(String currPkg, TaskInfo taskInfo) { + ActivityTaskManagerInternal m = + LocalServices.getService(ActivityTaskManagerInternal.class); + try { + // TODO(b/230616478) Investigate alternatives like ActivityMetricsLaunchObserver + List<ActivityManager.AppTask> tasks = + m.getAppTasks(currPkg, mPackageManager.getPackageUid(currPkg, 0)); + for (int i = 0; i < tasks.size(); i++) { + TaskInfo other = tasks.get(i).getTaskInfo(); + if (other.taskId != taskInfo.taskId && other.isFocused && other.isRunning + && isTaskStartedFromLauncher(currPkg, other)) { + return true; + } + } + } catch (PackageManager.NameNotFoundException e) { + // Fall through + } + return false; } private boolean isLauncherIntent(Intent intent) { @@ -1292,6 +1332,11 @@ public final class PermissionPolicyService extends SystemService { || intent.getCategories().contains(Intent.CATEGORY_CAR_LAUNCHER)); } + private boolean isTaskStartedFromLauncher(String currPkg, TaskInfo taskInfo) { + return currPkg.equals(taskInfo.baseActivity.getPackageName()) + && isLauncherIntent(taskInfo.baseIntent); + } + private void clearNotificationReviewFlagsIfNeeded(String packageName, UserHandle user) { if ((mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, packageName, user) & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) { diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java index 400460a1e656..34483957ca12 100644 --- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java +++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java @@ -61,6 +61,7 @@ public abstract class ActivityInterceptorCallback { PERMISSION_POLICY_ORDERED_ID, INTENT_RESOLVER_ORDERED_ID, VIRTUAL_DEVICE_SERVICE_ORDERED_ID, + DREAM_MANAGER_ORDERED_ID, LAST_ORDERED_ID // Update this when adding new ids }) @Retention(RetentionPolicy.SOURCE) @@ -88,10 +89,15 @@ public abstract class ActivityInterceptorCallback { public static final int VIRTUAL_DEVICE_SERVICE_ORDERED_ID = 3; /** + * The identifier for {@link com.android.server.dreams.DreamManagerService} interceptor. + */ + public static final int DREAM_MANAGER_ORDERED_ID = 4; + + /** * The final id, used by the framework to determine the valid range of ids. Update this when * adding new ids. */ - static final int LAST_ORDERED_ID = VIRTUAL_DEVICE_SERVICE_ORDERED_ID; + static final int LAST_ORDERED_ID = DREAM_MANAGER_ORDERED_ID; /** * Data class for storing the various arguments needed for activity interception. diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java index c6b17e24b1de..81e5fbd564e0 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java @@ -18,17 +18,19 @@ package com.android.server.wm; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; +import android.content.ComponentName; import android.content.Intent; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Observe activity manager launch sequences. + * Observe activity launch sequences. * - * The activity manager can have at most 1 concurrent launch sequences. Calls to this interface - * are ordered by a happens-before relation for each defined state transition (see below). + * Multiple calls to the callback methods can occur without first terminating the current launch + * sequence because activity can be launched concurrently. So the implementation should associate + * the corresponding event according to the timestamp from {@link #onIntentStarted} which is also + * used as the identifier to indicate which launch sequence it belongs to. * * When a new launch sequence is made, that sequence is in the {@code INTENT_STARTED} state which * is communicated by the {@link #onIntentStarted} callback. This is a transient state. @@ -47,7 +49,7 @@ import java.lang.annotation.RetentionPolicy; * Note this transition may not happen if the reportFullyDrawn event is not receivied, * in which case {@code FINISHED} is terminal. * - * Note that the {@code ActivityRecordProto} provided as a parameter to some state transitions isn't + * Note that the {@code ComponentName} provided as a parameter to some state transitions isn't * necessarily the same within a single launch sequence: it is only the top-most activity at the * time (if any). Trampoline activities coalesce several activity starts into a single launch * sequence. @@ -67,7 +69,7 @@ import java.lang.annotation.RetentionPolicy; * ╚════════════════╝ ╚═══════════════════════════╝ ╚═══════════════════════════╝ * </pre> */ -public interface ActivityMetricsLaunchObserver { +public class ActivityMetricsLaunchObserver { /** * The 'temperature' at which a launch sequence had started. * @@ -99,40 +101,31 @@ public interface ActivityMetricsLaunchObserver { public static final int TEMPERATURE_HOT = 3; /** - * Typedef marker that a {@code byte[]} actually contains an - * <a href="proto/android/server/activitymanagerservice.proto">ActivityRecordProto</a> - * in the protobuf format. - */ - @Retention(RetentionPolicy.SOURCE) - @interface ActivityRecordProto {} - - /** * Notifies the observer that a new launch sequence has begun as a result of a new intent. * * Once a launch sequence begins, the resolved activity will either subsequently start with * {@link #onActivityLaunched} or abort early (for example due to a resolution error or due to * a security error) with {@link #onIntentFailed}. * - * Multiple calls to this method cannot occur without first terminating the current - * launch sequence. + * @param timestampNanos The timestamp when receiving the intent. It is also use as an + * identifier for other callback methods to known which launch sequence + * it is associated with. */ - public void onIntentStarted(@NonNull Intent intent, long timestampNanos); + public void onIntentStarted(@NonNull Intent intent, long timestampNanos) { + } /** * Notifies the observer that the current launch sequence has failed to launch an activity. * - * This function call terminates the current launch sequence. The next method call, if any, - * must be {@link #onIntentStarted}. + * This function call terminates the current launch sequence. * * Examples of this happening: * - Failure to resolve to an activity * - Calling package did not have the security permissions to call the requested activity * - Resolved activity was already running and only needed to be brought to the top - * - * Multiple calls to this method cannot occur without first terminating the current - * launch sequence. */ - public void onIntentFailed(); + public void onIntentFailed(long id) { + } /** * Notifies the observer that the current launch sequence had begun starting an activity. @@ -145,62 +138,58 @@ public interface ActivityMetricsLaunchObserver { * necessarily the activity which will be considered as displayed when the activity * finishes launching (e.g. {@code activity} in {@link #onActivityLaunchFinished}). * - * Multiple calls to this method cannot occur without first terminating the current - * launch sequence. + * @param id The timestamp as an identifier from {@link #onIntentStarted}. It may be a new id + * if the launching activity is started from an existing launch sequence (trampoline) + * but cannot coalesce to the existing one, e.g. to a different display. + * @param name The launching activity name. */ - public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity, - @Temperature int temperature); + public void onActivityLaunched(long id, ComponentName name, @Temperature int temperature) { + } /** * Notifies the observer that the current launch sequence has been aborted. * - * This function call terminates the current launch sequence. The next method call, if any, - * must be {@link #onIntentStarted}. + * This function call terminates the current launch sequence. * * This can happen for many reasons, for example the user switches away to another app * prior to the launch sequence completing, or the application being killed. * - * Multiple calls to this method cannot occur without first terminating the current - * launch sequence. - * - * @param abortingActivity the last activity that had the top-most window during abort - * (this can be {@code null} in rare situations its unknown). + * @param id The timestamp as an identifier from {@link #onIntentStarted}. * * @apiNote The aborting activity isn't necessarily the same as the starting activity; * in the case of a trampoline, multiple activities could've been started * and only the latest activity is reported here. */ - public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] abortingActivity); + public void onActivityLaunchCancelled(long id) { + } /** * Notifies the observer that the current launch sequence has been successfully finished. * - * This function call terminates the current launch sequence. The next method call, if any, - * must be {@link #onIntentStarted}. + * This function call terminates the current launch sequence. * * A launch sequence is considered to be successfully finished when a frame is fully * drawn for the first time: the top-most activity at the time is what's reported here. * - * @param finalActivity the top-most activity whose windows were first to fully draw + * @param id The timestamp as an identifier from {@link #onIntentStarted}. + * @param name The name of drawn activity. It can be different from {@link #onActivityLaunched} + * if the transition contains multiple launching activities (e.g. trampoline). * @param timestampNanos the timestamp of ActivityLaunchFinished event in nanoseconds. * To compute the TotalTime duration, deduct the timestamp {@link #onIntentStarted} * from {@code timestampNanos}. * - * Multiple calls to this method cannot occur without first terminating the current - * launch sequence. - * * @apiNote The finishing activity isn't necessarily the same as the starting activity; * in the case of a trampoline, multiple activities could've been started * and only the latest activity that was top-most during first-frame drawn * is reported here. */ - public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] finalActivity, - long timestampNanos); + public void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos) { + } /** * Notifies the observer that the application self-reported itself as being fully drawn. * - * @param activity the activity that triggers the ReportFullyDrawn event. + * @param id The timestamp as an identifier from {@link #onIntentStarted}. * @param timestampNanos the timestamp of ReportFullyDrawn event in nanoseconds. * To compute the duration, deduct the deduct the timestamp {@link #onIntentStarted} * from {@code timestampNanos}. @@ -209,7 +198,7 @@ public interface ActivityMetricsLaunchObserver { * It is used as an accurate estimate of meanfully app startup time. * This event may be missing for many apps. */ - public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity, - long timestampNanos); + public void onReportFullyDrawn(long id, long timestampNanos) { + } } diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 7f84f61a91ff..1ea08f5cfea2 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -97,7 +97,6 @@ import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; -import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -176,7 +175,6 @@ class ActivityMetricsLogger { * in-order on the same thread to fulfill the "happens-before" guarantee in LaunchObserver. */ private final LaunchObserverRegistryImpl mLaunchObserver; - @VisibleForTesting static final int LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE = 512; private final ArrayMap<String, Boolean> mLastHibernationStates = new ArrayMap<>(); private AppHibernationManagerInternal mAppHibernationManagerInternal; @@ -675,7 +673,7 @@ class ActivityMetricsLogger { launchObserverNotifyActivityLaunched(newInfo); } else { // As abort for no process switch. - launchObserverNotifyIntentFailed(); + launchObserverNotifyIntentFailed(newInfo.mTransitionStartTimeNs); } scheduleCheckActivityToBeDrawnIfSleeping(launchedActivity); @@ -910,7 +908,7 @@ class ActivityMetricsLogger { } if (DEBUG_METRICS) Slog.i(TAG, "abort launch cause=" + cause); state.stopTrace(true /* abort */); - launchObserverNotifyIntentFailed(); + launchObserverNotifyIntentFailed(state.mCurrentTransitionStartTimeNs); } /** Aborts tracking of current launch metrics. */ @@ -1187,7 +1185,7 @@ class ActivityMetricsLogger { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); // Notify reportFullyDrawn event. - launchObserverNotifyReportFullyDrawn(r, currentTimestampNs); + launchObserverNotifyReportFullyDrawn(info, currentTimestampNs); return infoSnapshot; } @@ -1531,11 +1529,11 @@ class ActivityMetricsLogger { * aborted due to intent failure (e.g. intent resolve failed or security error, etc) or * intent being delivered to the top running activity. */ - private void launchObserverNotifyIntentFailed() { + private void launchObserverNotifyIntentFailed(long id) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "MetricsLogger:launchObserverNotifyIntentFailed"); - mLaunchObserver.onIntentFailed(); + mLaunchObserver.onIntentFailed(id); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } @@ -1552,8 +1550,8 @@ class ActivityMetricsLogger { convertTransitionTypeToLaunchObserverTemperature(info.mTransitionType); // Beginning a launch is timing sensitive and so should be observed as soon as possible. - mLaunchObserver.onActivityLaunched(convertActivityRecordToProto(info.mLastLaunchedActivity), - temperature); + mLaunchObserver.onActivityLaunched(info.mTransitionStartTimeNs, + info.mLastLaunchedActivity.mActivityComponent, temperature); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } @@ -1561,10 +1559,10 @@ class ActivityMetricsLogger { /** * Notifies the {@link ActivityMetricsLaunchObserver} the reportFullDrawn event. */ - private void launchObserverNotifyReportFullyDrawn(ActivityRecord r, long timestampNs) { + private void launchObserverNotifyReportFullyDrawn(TransitionInfo info, long timestampNs) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "MetricsLogger:launchObserverNotifyReportFullyDrawn"); - mLaunchObserver.onReportFullyDrawn(convertActivityRecordToProto(r), timestampNs); + mLaunchObserver.onReportFullyDrawn(info.mTransitionStartTimeNs, timestampNs); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } @@ -1576,10 +1574,7 @@ class ActivityMetricsLogger { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "MetricsLogger:launchObserverNotifyActivityLaunchCancelled"); - final @ActivityMetricsLaunchObserver.ActivityRecordProto byte[] activityRecordProto = - info != null ? convertActivityRecordToProto(info.mLastLaunchedActivity) : null; - - mLaunchObserver.onActivityLaunchCancelled(activityRecordProto); + mLaunchObserver.onActivityLaunchCancelled(info.mTransitionStartTimeNs); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } @@ -1592,31 +1587,10 @@ class ActivityMetricsLogger { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "MetricsLogger:launchObserverNotifyActivityLaunchFinished"); - mLaunchObserver.onActivityLaunchFinished( - convertActivityRecordToProto(info.mLastLaunchedActivity), timestampNs); - - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } - - @VisibleForTesting - static @ActivityMetricsLaunchObserver.ActivityRecordProto byte[] - convertActivityRecordToProto(ActivityRecord record) { - // May take non-negligible amount of time to convert ActivityRecord into a proto, - // so track the time. - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, - "MetricsLogger:convertActivityRecordToProto"); - - // There does not appear to be a way to 'reset' a ProtoOutputBuffer stream, - // so create a new one every time. - final ProtoOutputStream protoOutputStream = - new ProtoOutputStream(LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE); - // Write this data out as the top-most ActivityRecordProto (i.e. it is not a sub-object). - record.dumpDebug(protoOutputStream, WindowTraceLogLevel.ALL); - final byte[] bytes = protoOutputStream.getBytes(); + mLaunchObserver.onActivityLaunchFinished(info.mTransitionStartTimeNs, + info.mLastLaunchedActivity.mActivityComponent, timestampNs); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - - return bytes; } private static @ActivityMetricsLaunchObserver.Temperature int diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index b2f9b52cad8d..d00d8b8c9f8a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2447,6 +2447,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated, boolean activityAllDrawn, TaskSnapshot snapshot) { + // A special case that a new activity is launching to an existing task which is moving to + // front. If the launching activity is the one that started the task, it could be a + // trampoline that will be always created and finished immediately. Then give a chance to + // see if the snapshot is usable for the current running activity so the transition will + // look smoother, instead of showing a splash screen on the second launch. + if (!newTask && taskSwitch && processRunning && !activityCreated && task.intent != null + && mActivityComponent.equals(task.intent.getComponent())) { + final ActivityRecord topAttached = task.getActivity(ActivityRecord::attachedToProcess); + if (topAttached != null && topAttached.isSnapshotCompatible(snapshot)) { + return STARTING_WINDOW_TYPE_SNAPSHOT; + } + } final boolean isActivityHome = isActivityTypeHome(); if ((newTask || !processRunning || (taskSwitch && !activityCreated)) && !isActivityHome) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index d254aaff1a1c..41b724f2596f 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -64,6 +64,7 @@ import static android.provider.Settings.Global.HIDE_ERROR_DIALOGS; import static android.provider.Settings.System.FONT_SCALE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT; import static android.view.WindowManager.TRANSIT_WAKE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; @@ -3398,6 +3399,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final long token = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { + // Keyguard asked us to clear the home task snapshot before going away, so do that. + if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT) != 0) { + mActivityClientController.invalidateHomeTaskSnapshot(null /* token */); + } + mRootWindowContainer.forAllDisplays(displayContent -> { mKeyguardController.keyguardGoingAway(displayContent.getDisplayId(), flags); }); diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 6e46fa6b67d0..e80c2607a0ad 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -27,6 +27,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; +import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; @@ -48,8 +49,9 @@ class AppTaskImpl extends IAppTask.Stub { mCallingUid = callingUid; } - private void checkCaller() { - if (mCallingUid != Binder.getCallingUid()) { + private void checkCallerOrSystemOrRoot() { + if (mCallingUid != Binder.getCallingUid() && Process.SYSTEM_UID != Binder.getCallingUid() + && Process.ROOT_UID != Binder.getCallingUid()) { throw new SecurityException("Caller " + mCallingUid + " does not match caller of getAppTasks(): " + Binder.getCallingUid()); } @@ -67,7 +69,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public void finishAndRemoveTask() { - checkCaller(); + checkCallerOrSystemOrRoot(); synchronized (mService.mGlobalLock) { final long origId = Binder.clearCallingIdentity(); @@ -85,7 +87,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public ActivityManager.RecentTaskInfo getTaskInfo() { - checkCaller(); + checkCallerOrSystemOrRoot(); synchronized (mService.mGlobalLock) { final long origId = Binder.clearCallingIdentity(); @@ -105,7 +107,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public void moveToFront(IApplicationThread appThread, String callingPackage) { - checkCaller(); + checkCallerOrSystemOrRoot(); // Will bring task to front if it already has a root activity. final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); @@ -136,7 +138,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public int startActivity(IBinder whoThread, String callingPackage, String callingFeatureId, Intent intent, String resolvedType, Bundle bOptions) { - checkCaller(); + checkCallerOrSystemOrRoot(); mService.assertPackageMatchesCallingUid(callingPackage); int callingUser = UserHandle.getCallingUserId(); @@ -167,7 +169,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public void setExcludeFromRecents(boolean exclude) { - checkCaller(); + checkCallerOrSystemOrRoot(); synchronized (mService.mGlobalLock) { final long origId = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 4f015d8d77a0..5410dd8508f1 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; @@ -1258,6 +1259,9 @@ public class AppTransition implements Dump { "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER")); sFlagToString.add(new Pair<>(TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION, "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION")); + sFlagToString.add(new Pair<>( + TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT, + "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_WITH_IN_WINDOW_ANIMATIONS")); sFlagToString.add(new Pair<>(TRANSIT_FLAG_APP_CRASHED, "TRANSIT_FLAG_APP_CRASHED")); sFlagToString.add(new Pair<>(TRANSIT_FLAG_OPEN_BEHIND, diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 3bda2e60334a..701fc9441acb 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -912,6 +912,15 @@ public class AppTransitionController { canPromote = false; } + // If the current window container is task and it have adjacent task, it means + // both tasks will open or close app toghther but we want get their opening or + // closing animation target independently so do not promote. + if (current.asTask() != null + && current.asTask().getAdjacentTaskFragment() != null + && current.asTask().getAdjacentTaskFragment().asTask() != null) { + canPromote = false; + } + // Find all siblings of the current WindowContainer in "candidates", move them into // a separate list "siblings", and checks if an animation target can be promoted // to its parent. diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index dc441860f7c8..ed1bbf8e4b74 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -60,6 +60,7 @@ import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; +import static android.view.WindowManager.LayoutParams; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; @@ -4291,18 +4292,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Update Ime parent when IME insets leash created or the new IME layering target might // updated from setImeLayeringTarget, which is the best time that default IME visibility // has been settled down after IME control target changed. - final boolean imeParentChanged = - prevImeControlTarget != mImeControlTarget || forceUpdateImeParent; - if (imeParentChanged) { + final boolean imeControlChanged = prevImeControlTarget != mImeControlTarget; + if (imeControlChanged || forceUpdateImeParent) { updateImeParent(); } final WindowState win = InsetsControlTarget.asWindowOrNull(mImeControlTarget); final IBinder token = win != null ? win.mClient.asBinder() : null; // Note: not allowed to call into IMMS with the WM lock held, hence the post. - mWmService.mH.post(() -> - InputMethodManagerInternal.get().reportImeControl(token, imeParentChanged) - ); + mWmService.mH.post(() -> InputMethodManagerInternal.get().reportImeControl(token)); } void updateImeParent() { @@ -4324,6 +4322,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // do a force update to make sure there is a layer set for the new parent. assignRelativeLayerForIme(getSyncTransaction(), true /* forceUpdate */); scheduleAnimation(); + + mWmService.mH.post(() -> InputMethodManagerInternal.get().onImeParentChanged()); } } @@ -4347,10 +4347,24 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ @VisibleForTesting SurfaceControl computeImeParent() { - if (mImeLayeringTarget != null && mImeInputTarget != null - && mImeLayeringTarget.mActivityRecord != mImeInputTarget.getActivityRecord()) { - // Do not change parent if the window hasn't requested IME. - return null; + if (mImeLayeringTarget != null) { + // Ensure changing the IME parent when the layering target that may use IME has + // became to the input target for preventing IME flickers. + // Note that: + // 1) For the imeLayeringTarget that may not use IME but requires IME on top + // of it (e.g. an overlay window with NOT_FOCUSABLE|ALT_FOCUSABLE_IM flags), we allow + // it to re-parent the IME on top the display to keep the legacy behavior. + // 2) Even though the starting window won't use IME, the associated activity + // behind the starting window may request the input. If so, then we should still hold + // the IME parent change until the activity started the input. + boolean imeLayeringTargetMayUseIme = + LayoutParams.mayUseInputMethod(mImeLayeringTarget.mAttrs.flags) + || mImeLayeringTarget.mAttrs.type == TYPE_APPLICATION_STARTING; + if (imeLayeringTargetMayUseIme && mImeInputTarget != null + && mImeLayeringTarget.mActivityRecord != mImeInputTarget.getActivityRecord()) { + // Do not change parent if the window hasn't requested IME. + return null; + } } // Attach it to app if the target is part of an app and such app is covering the entire // screen. If it's not covering the entire screen the IME might extend beyond the apps diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java index 76aa7f963aa6..fd0631320520 100644 --- a/services/core/java/com/android/server/wm/DisplayFrames.java +++ b/services/core/java/com/android/server/wm/DisplayFrames.java @@ -83,7 +83,7 @@ public class DisplayFrames { final Rect safe = mDisplayCutoutSafe; final DisplayCutout cutout = displayCutout.getDisplayCutout(); if (mDisplayWidth == info.logicalWidth && mDisplayHeight == info.logicalHeight - && mRotation != info.rotation + && mRotation == info.rotation && state.getDisplayCutout().equals(cutout) && state.getRoundedCorners().equals(roundedCorners) && state.getPrivacyIndicatorBounds().equals(indicatorBounds)) { diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 2ebb59751634..f36dbfa2316e 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -23,6 +23,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; @@ -32,6 +33,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS; +import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; @@ -309,6 +311,10 @@ class KeyguardController { if ((keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS) != 0) { result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; } + if ((keyguardGoingAwayFlags + & KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT) != 0) { + result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT; + } return result; } diff --git a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java index 362ed3c380c5..9cbc1bdcbeeb 100644 --- a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java +++ b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java @@ -16,12 +16,11 @@ package com.android.server.wm; +import android.content.ComponentName; import android.content.Intent; import android.os.Handler; import android.os.Looper; -import android.os.Message; -import com.android.internal.os.BackgroundThread; import com.android.internal.util.function.pooled.PooledLambda; import java.util.ArrayList; @@ -39,8 +38,8 @@ import java.util.ArrayList; * * @see ActivityTaskManagerInternal#getLaunchObserverRegistry() */ -class LaunchObserverRegistryImpl implements - ActivityMetricsLaunchObserverRegistry, ActivityMetricsLaunchObserver { +class LaunchObserverRegistryImpl extends ActivityMetricsLaunchObserver implements + ActivityMetricsLaunchObserverRegistry { private final ArrayList<ActivityMetricsLaunchObserver> mList = new ArrayList<>(); /** @@ -79,45 +78,36 @@ class LaunchObserverRegistryImpl implements } @Override - public void onIntentFailed() { + public void onIntentFailed(long id) { mHandler.sendMessage(PooledLambda.obtainMessage( - LaunchObserverRegistryImpl::handleOnIntentFailed, this)); + LaunchObserverRegistryImpl::handleOnIntentFailed, this, id)); } @Override - public void onActivityLaunched( - @ActivityRecordProto byte[] activity, - int temperature) { + public void onActivityLaunched(long id, ComponentName name, int temperature) { mHandler.sendMessage(PooledLambda.obtainMessage( LaunchObserverRegistryImpl::handleOnActivityLaunched, - this, activity, temperature)); + this, id, name, temperature)); } @Override - public void onActivityLaunchCancelled( - @ActivityRecordProto byte[] activity) { + public void onActivityLaunchCancelled(long id) { mHandler.sendMessage(PooledLambda.obtainMessage( - LaunchObserverRegistryImpl::handleOnActivityLaunchCancelled, this, activity)); + LaunchObserverRegistryImpl::handleOnActivityLaunchCancelled, this, id)); } @Override - public void onActivityLaunchFinished( - @ActivityRecordProto byte[] activity, - long timestampNs) { + public void onActivityLaunchFinished(long id, ComponentName name, long timestampNs) { mHandler.sendMessage(PooledLambda.obtainMessage( - LaunchObserverRegistryImpl::handleOnActivityLaunchFinished, - this, - activity, - timestampNs)); + LaunchObserverRegistryImpl::handleOnActivityLaunchFinished, + this, id, name, timestampNs)); } @Override - public void onReportFullyDrawn(@ActivityRecordProto byte[] activity, long timestampNs) { + public void onReportFullyDrawn(long id, long timestampNs) { mHandler.sendMessage(PooledLambda.obtainMessage( - LaunchObserverRegistryImpl::handleOnReportFullyDrawn, - this, - activity, - timestampNs)); + LaunchObserverRegistryImpl::handleOnReportFullyDrawn, + this, id, timestampNs)); } // Use PooledLambda.obtainMessage to invoke below methods. Every method reference must be @@ -135,53 +125,43 @@ class LaunchObserverRegistryImpl implements private void handleOnIntentStarted(Intent intent, long timestampNs) { // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee. for (int i = 0; i < mList.size(); i++) { - ActivityMetricsLaunchObserver o = mList.get(i); - o.onIntentStarted(intent, timestampNs); + mList.get(i).onIntentStarted(intent, timestampNs); } } - private void handleOnIntentFailed() { + private void handleOnIntentFailed(long id) { // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee. for (int i = 0; i < mList.size(); i++) { - ActivityMetricsLaunchObserver o = mList.get(i); - o.onIntentFailed(); + mList.get(i).onIntentFailed(id); } } - private void handleOnActivityLaunched( - @ActivityRecordProto byte[] activity, + private void handleOnActivityLaunched(long id, ComponentName name, @Temperature int temperature) { // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee. for (int i = 0; i < mList.size(); i++) { - ActivityMetricsLaunchObserver o = mList.get(i); - o.onActivityLaunched(activity, temperature); + mList.get(i).onActivityLaunched(id, name, temperature); } } - private void handleOnActivityLaunchCancelled( - @ActivityRecordProto byte[] activity) { + private void handleOnActivityLaunchCancelled(long id) { // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee. for (int i = 0; i < mList.size(); i++) { - ActivityMetricsLaunchObserver o = mList.get(i); - o.onActivityLaunchCancelled(activity); + mList.get(i).onActivityLaunchCancelled(id); } } - private void handleOnActivityLaunchFinished( - @ActivityRecordProto byte[] activity, long timestampNs) { + private void handleOnActivityLaunchFinished(long id, ComponentName name, long timestampNs) { // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee. for (int i = 0; i < mList.size(); i++) { - ActivityMetricsLaunchObserver o = mList.get(i); - o.onActivityLaunchFinished(activity, timestampNs); + mList.get(i).onActivityLaunchFinished(id, name, timestampNs); } } - private void handleOnReportFullyDrawn( - @ActivityRecordProto byte[] activity, long timestampNs) { + private void handleOnReportFullyDrawn(long id, long timestampNs) { // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee. for (int i = 0; i < mList.size(); i++) { - ActivityMetricsLaunchObserver o = mList.get(i); - o.onReportFullyDrawn(activity, timestampNs); + mList.get(i).onReportFullyDrawn(id, timestampNs); } } } diff --git a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java index 11a27c593d9e..3f6fb622481f 100644 --- a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java +++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java @@ -16,15 +16,11 @@ package com.android.server.wm; -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_270; - import android.hardware.display.DisplayManagerInternal; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.view.DisplayInfo; -import android.view.Surface; import java.util.Set; @@ -44,8 +40,7 @@ public class PossibleDisplayInfoMapper { /** * Map of all logical displays, indexed by logical display id. - * Each logical display has multiple entries, one for each possible rotation and device - * state. + * Each logical display has multiple entries, one for each device state. * * Emptied and re-calculated when a display is added, removed, or changed. */ @@ -57,8 +52,8 @@ public class PossibleDisplayInfoMapper { /** - * Returns, for the given displayId, a set of display infos. Set contains the possible rotations - * for each supported device state. + * Returns, for the given displayId, a set of display infos. Set contains each supported device + * state. */ public Set<DisplayInfo> getPossibleDisplayInfos(int displayId) { // Update display infos before returning, since any cached values would have been removed @@ -73,13 +68,13 @@ public class PossibleDisplayInfoMapper { } /** - * Updates the possible {@link DisplayInfo}s for the given display, by calculating the - * DisplayInfo for each rotation across supported device states. + * Updates the possible {@link DisplayInfo}s for the given display, by saving the DisplayInfo + * across supported device states. */ public void updatePossibleDisplayInfos(int displayId) { Set<DisplayInfo> displayInfos = mDisplayManagerInternal.getPossibleDisplayInfo(displayId); if (DEBUG) { - Slog.v(TAG, "updatePossibleDisplayInfos, calculate rotations for given DisplayInfo " + Slog.v(TAG, "updatePossibleDisplayInfos, given DisplayInfo " + displayInfos.size() + " on display " + displayId); } updateDisplayInfos(displayInfos); @@ -99,40 +94,12 @@ public class PossibleDisplayInfoMapper { private void updateDisplayInfos(Set<DisplayInfo> displayInfos) { // Empty out cache before re-computing. mDisplayInfos.clear(); - DisplayInfo[] originalDisplayInfos = new DisplayInfo[displayInfos.size()]; - displayInfos.toArray(originalDisplayInfos); // Iterate over each logical display layout for the current state. - Set<DisplayInfo> rotatedDisplayInfos; - for (DisplayInfo di : originalDisplayInfos) { - rotatedDisplayInfos = new ArraySet<>(); - // Calculate all possible rotations for each logical display. - for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) { - rotatedDisplayInfos.add(applyRotation(di, rotation)); - } + for (DisplayInfo di : displayInfos) { // Combine all results under the logical display id. Set<DisplayInfo> priorDisplayInfos = mDisplayInfos.get(di.displayId, new ArraySet<>()); - priorDisplayInfos.addAll(rotatedDisplayInfos); + priorDisplayInfos.add(di); mDisplayInfos.put(di.displayId, priorDisplayInfos); } } - - private static DisplayInfo applyRotation(DisplayInfo displayInfo, - @Surface.Rotation int rotation) { - DisplayInfo updatedDisplayInfo = new DisplayInfo(); - updatedDisplayInfo.copyFrom(displayInfo); - // Apply rotations before updating width and height - updatedDisplayInfo.roundedCorners = updatedDisplayInfo.roundedCorners.rotate(rotation, - updatedDisplayInfo.logicalWidth, updatedDisplayInfo.logicalHeight); - updatedDisplayInfo.displayCutout = - DisplayContent.calculateDisplayCutoutForRotationAndDisplaySizeUncached( - updatedDisplayInfo.displayCutout, rotation, updatedDisplayInfo.logicalWidth, - updatedDisplayInfo.logicalHeight).getDisplayCutout(); - - updatedDisplayInfo.rotation = rotation; - final int naturalWidth = updatedDisplayInfo.getNaturalWidth(); - final int naturalHeight = updatedDisplayInfo.getNaturalHeight(); - updatedDisplayInfo.logicalWidth = naturalWidth; - updatedDisplayInfo.logicalHeight = naturalHeight; - return updatedDisplayInfo; - } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index ca4c450a4592..c0dff14e5de5 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -3382,7 +3382,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (record != null && record.isUid(uid) && Objects.equals(pkgName, record.packageName) && pPi.shouldShowNotificationDialogForTask(record.getTask().getTaskInfo(), - pkgName, record.intent)) { + pkgName, record.launchedFromPackage, record.intent, record.getName())) { validTaskId[0] = record.getTask().mTaskId; return true; } diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index 65ae3fcb4c90..b4029d185b9f 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -219,7 +219,7 @@ class ScreenRotationAnimation { // If hdr layers are on-screen, e.g. picture-in-picture mode, the screenshot of // rotation animation is an sdr image containing tone-mapping hdr content, then // disable dimming effect to get avoid of hdr content being dimmed during animation. - t.setDimmingEnabled(mScreenshotLayer, false); + t.setDimmingEnabled(mScreenshotLayer, !screenshotBuffer.containsHdrLayers()); t.setLayer(mBackColorSurface, -1); t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma}); t.setAlpha(mBackColorSurface, 1); diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index 813e06fecf48..ccd018faf075 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -26,6 +26,7 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING; import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH; import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN; +import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_TYPE_SNAPSHOT; import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_TYPE_SPLASH_SCREEN; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -114,7 +115,7 @@ public class StartingSurfaceController { if (allowTaskSnapshot) { parameter |= TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT; } - if (activityCreated) { + if (activityCreated || startingWindowType == STARTING_WINDOW_TYPE_SNAPSHOT) { parameter |= TYPE_PARAMETER_ACTIVITY_CREATED; } if (isSolidColor) { @@ -138,7 +139,6 @@ public class StartingSurfaceController { final WindowState topFullscreenOpaqueWindow; final Task task; synchronized (mService.mGlobalLock) { - final WindowState mainWindow = activity.findMainWindow(); task = activity.getTask(); if (task == null) { Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find task for activity=" @@ -153,9 +153,9 @@ public class StartingSurfaceController { return null; } topFullscreenOpaqueWindow = topFullscreenActivity.getTopFullscreenOpaqueWindow(); - if (mainWindow == null || topFullscreenOpaqueWindow == null) { - Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find main window for activity=" - + activity); + if (topFullscreenOpaqueWindow == null) { + Slog.w(TAG, "TaskSnapshotSurface.create: no opaque window in " + + topFullscreenActivity); return null; } if (topFullscreenActivity.getWindowConfiguration().getRotation() diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 17e1dd26a602..00e61171cb68 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -56,6 +56,7 @@ import static android.view.SurfaceControl.METADATA_TASK_ID; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; @@ -3525,10 +3526,13 @@ class Task extends TaskFragment { mAtmService.mKeyguardController.isDisplayOccluded(DEFAULT_DISPLAY); info.startingWindowTypeParameter = activity.mStartingData.mTypeParams; - final WindowState mainWindow = activity.findMainWindow(); - if (mainWindow != null) { - info.mainWindowLayoutParams = mainWindow.getAttrs(); - info.requestedVisibilities.set(mainWindow.getRequestedVisibilities()); + if ((info.startingWindowTypeParameter + & StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED) != 0) { + final WindowState topMainWin = getWindow(w -> w.mAttrs.type == TYPE_BASE_APPLICATION); + if (topMainWin != null) { + info.mainWindowLayoutParams = topMainWin.getAttrs(); + info.requestedVisibilities.set(topMainWin.getRequestedVisibilities()); + } } // If the developer has persist a different configuration, we need to override it to the // starting window because persisted configuration does not effect to Task. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a9b0f0dc28ad..48168a08a543 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8755,8 +8755,7 @@ public class WindowManagerService extends IWindowManager.Stub return new ArrayList<>(); } - // Retrieve the DisplayInfo for all possible rotations across all possible display - // layouts. + // Retrieve the DisplayInfo across all possible display layouts. return List.copyOf(mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId)); } } finally { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index c6288a7da26a..cd19f64ba12e 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2464,7 +2464,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP dc.setImeLayeringTarget(null); dc.computeImeTarget(true /* updateImeTarget */); } - if (dc.getImeInputTarget() == this) { + if (dc.getImeInputTarget() == this + && (mActivityRecord == null || !mActivityRecord.isRelaunching())) { dc.updateImeInputAndControlTarget(null); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index d1fac871fa2e..0a426eb80c54 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -13057,9 +13057,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (callerPackage == null) { return false; } + + CallerIdentity caller = new CallerIdentity(callerUid, null, null); if (isUserAffiliatedWithDevice(UserHandle.getUserId(callerUid)) && (isActiveProfileOwner(callerUid) - || isActiveDeviceOwner(callerUid))) { + || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller))) { // device owner or a profile owner affiliated with the device owner return true; } diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index bbc28d78d6f2..d3222901db1e 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -274,36 +274,11 @@ public final class ProfcollectForwardingService extends SystemService { } } - private class AppLaunchObserver implements ActivityMetricsLaunchObserver { + private class AppLaunchObserver extends ActivityMetricsLaunchObserver { @Override public void onIntentStarted(Intent intent, long timestampNanos) { traceOnAppStart(intent.getPackage()); } - - @Override - public void onIntentFailed() { - // Ignored - } - - @Override - public void onActivityLaunched(byte[] activity, int temperature) { - // Ignored - } - - @Override - public void onActivityLaunchCancelled(byte[] abortingActivity) { - // Ignored - } - - @Override - public void onActivityLaunchFinished(byte[] finalActivity, long timestampNanos) { - // Ignored - } - - @Override - public void onReportFullyDrawn(byte[] activity, long timestampNanos) { - // Ignored - } } private void registerOTAObserver() { diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 529def3697cd..8461b39f8899 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -2758,7 +2758,7 @@ public class AlarmManagerServiceTest { mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false); // No permission revoked. - verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(anyInt(), anyString(), + verify(mService, never()).removeExactAlarmsOnPermissionRevoked(anyInt(), anyString(), anyBoolean()); // Permission got granted only for (appId1, userId2). @@ -2813,14 +2813,14 @@ public class AlarmManagerServiceTest { mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), true); // Permission got revoked only for (appId1, userId2) - verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( + verify(mService, never()).removeExactAlarmsOnPermissionRevoked( eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true)); - verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( + verify(mService, never()).removeExactAlarmsOnPermissionRevoked( eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true)); - verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( + verify(mService, never()).removeExactAlarmsOnPermissionRevoked( eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true)); - verify(mService).removeExactAlarmsOnPermissionRevokedLocked( + verify(mService).removeExactAlarmsOnPermissionRevoked( eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true)); } @@ -2833,7 +2833,7 @@ public class AlarmManagerServiceTest { mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); assertAndHandleMessageSync(REMOVE_EXACT_ALARMS); - verify(mService).removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, + verify(mService).removeExactAlarmsOnPermissionRevoked(TEST_CALLING_UID, TEST_CALLING_PACKAGE, true); } @@ -2919,7 +2919,7 @@ public class AlarmManagerServiceTest { null); assertEquals(6, mService.mAlarmStore.size()); - mService.removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, TEST_CALLING_PACKAGE, + mService.removeExactAlarmsOnPermissionRevoked(TEST_CALLING_UID, TEST_CALLING_PACKAGE, true); final ArrayList<Alarm> remaining = mService.mAlarmStore.asList(); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index c747a5fd982b..2f68306e9ba1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -191,6 +191,8 @@ public class MockingOomAdjusterTests { mock(ActivityManagerService.LocalService.class)); setFieldValue(ActivityManagerService.class, sService, "mBatteryStatsService", mock(BatteryStatsService.class)); + setFieldValue(ActivityManagerService.class, sService, "mInjector", + new ActivityManagerService.Injector(sContext)); doReturn(mock(AppOpsManager.class)).when(sService).getAppOpsManager(); doCallRealMethod().when(sService).enqueueOomAdjTargetLocked(any(ProcessRecord.class)); doCallRealMethod().when(sService).updateOomAdjPendingTargetsLocked(any(String.class)); diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index e3be3a792549..16df5deb2e5c 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -19,9 +19,6 @@ android_test { "src/**/*.java", "src/**/*.kt", - "aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl", - "aidl/com/android/servicestests/aidl/ICmdReceiverService.aidl", - "test-apps/JobTestApp/src/**/*.java", "test-apps/SuspendTestApp/src/**/*.java", @@ -67,10 +64,6 @@ android_test { "ActivityContext", ], - aidl: { - local_include_dirs: ["aidl"], - }, - libs: [ "android.hardware.power-V1-java", "android.hardware.tv.cec-V1.0-java", diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index bb3eb81df6ed..158bd39a4fd0 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -28,7 +28,6 @@ <option name="install-arg" value="-t" /> <option name="test-file-name" value="FrameworksServicesTests.apk" /> <option name="test-file-name" value="JobTestApp.apk" /> - <option name="test-file-name" value="ConnTestApp.apk" /> <option name="test-file-name" value="SuspendTestApp.apk" /> <option name="test-file-name" value="SimpleServiceTestApp1.apk" /> <option name="test-file-name" value="SimpleServiceTestApp2.apk" /> diff --git a/services/tests/servicestests/aidl/Android.bp b/services/tests/servicestests/aidl/Android.bp deleted file mode 100644 index 678053192e82..000000000000 --- a/services/tests/servicestests/aidl/Android.bp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (C) 2017 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -java_library { - name: "servicestests-aidl", - sdk_version: "current", - srcs: [ - "com/android/servicestests/aidl/INetworkStateObserver.aidl", - "com/android/servicestests/aidl/ICmdReceiverService.aidl", - ], -} diff --git a/services/tests/servicestests/aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl b/services/tests/servicestests/aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl deleted file mode 100644 index ca9fc4c439d2..000000000000 --- a/services/tests/servicestests/aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.servicestests.aidl; - -oneway interface INetworkStateObserver { - /** - * {@param resultData} will be in the format - * NetinfoState|NetinfoDetailedState|RealConnectionCheck|RealConnectionCheckDetails|Netinfo. - * For detailed info, see - * servicestests/test-apps/ConnTestApp/.../ConnTestActivity#checkNetworkStatus - */ - void onNetworkStateChecked(String resultData); -}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java index 5c91b8b4717f..d1390c68e130 100644 --- a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java +++ b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java @@ -90,6 +90,16 @@ public class DropboxRateLimiterTest { mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated()); assertEquals(2, mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated()); + + // After 11 seconds the rate limiting buffer will be cleared and rate limiting will stop. + mClock.setOffsetMillis(11000); + + // The first call after rate limiting stops will still return the number of dropped events. + assertEquals(2, + mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated()); + // The next call should show that the dropped event counter was reset. + assertEquals(0, + mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated()); } private static class TestClock implements DropboxRateLimiter.Clock { diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index bf3c7c3e05fb..d5612e7a27b2 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -38,6 +39,7 @@ import android.compat.testing.PlatformCompatChangeRule; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Rect; import android.hardware.display.BrightnessConfiguration; @@ -72,6 +74,7 @@ import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.R; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; @@ -93,6 +96,7 @@ import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.time.Duration; @@ -203,6 +207,10 @@ public class DisplayManagerServiceTest { @Test public void testCreateVirtualDisplay_sentToInputManager() { + // This is to update the display device config such that DisplayManagerService can ignore + // the usage of SensorManager, which is available only after the PowerManagerService + // is ready. + resetConfigToIgnoreSensorManager(mContext); DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); registerDefaultDisplays(displayManager); @@ -275,6 +283,10 @@ public class DisplayManagerServiceTest { @Test public void testPhysicalViewports() { + // This is to update the display device config such that DisplayManagerService can ignore + // the usage of SensorManager, which is available only after the PowerManagerService + // is ready. + resetConfigToIgnoreSensorManager(mContext); DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); registerDefaultDisplays(displayManager); @@ -1343,6 +1355,20 @@ public class DisplayManagerServiceTest { } } + private void resetConfigToIgnoreSensorManager(Context context) { + final Resources res = Mockito.spy(context.getResources()); + doReturn(new int[]{-1}).when(res).getIntArray(R.array + .config_ambientThresholdsOfPeakRefreshRate); + doReturn(new int[]{-1}).when(res).getIntArray(R.array + .config_brightnessThresholdsOfPeakRefreshRate); + doReturn(new int[]{-1}).when(res).getIntArray(R.array + .config_highDisplayBrightnessThresholdsOfFixedRefreshRate); + doReturn(new int[]{-1}).when(res).getIntArray(R.array + .config_highAmbientBrightnessThresholdsOfFixedRefreshRate); + + when(context.getResources()).thenReturn(res); + } + private class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub { int mDisplayId; boolean mDisplayAddedCalled = false; diff --git a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java deleted file mode 100644 index 25b41db1aea3..000000000000 --- a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.net; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.os.BatteryManager; -import android.os.Bundle; -import android.os.IBinder; -import android.os.SystemClock; -import android.support.test.uiautomator.UiDevice; -import android.text.TextUtils; -import android.util.Log; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.servicestests.aidl.ICmdReceiverService; -import com.android.servicestests.aidl.INetworkStateObserver; - -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.IOException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * Tests for verifying network availability on activity start. - * - * To run the tests, use - * - * runtest -c com.android.server.net.ConnOnActivityStartTest frameworks-services - * - * or the following steps: - * - * Build: m FrameworksServicesTests - * Install: adb install -r \ - * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk - * Run: adb shell am instrument -e class com.android.server.net.ConnOnActivityStartTest -w \ - * com.android.frameworks.servicestests/androidx.test.runner.AndroidJUnitRunner - */ -@LargeTest -@RunWith(AndroidJUnit4.class) -public class ConnOnActivityStartTest { - private static final String TAG = ConnOnActivityStartTest.class.getSimpleName(); - - private static final String TEST_PKG = "com.android.servicestests.apps.conntestapp"; - private static final String TEST_ACTIVITY_CLASS = TEST_PKG + ".ConnTestActivity"; - private static final String TEST_SERVICE_CLASS = TEST_PKG + ".CmdReceiverService"; - - private static final String EXTRA_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer"; - - private static final long BATTERY_OFF_TIMEOUT_MS = 2000; // 2 sec - private static final long BATTERY_OFF_CHECK_INTERVAL_MS = 200; // 0.2 sec - - private static final long NETWORK_CHECK_TIMEOUT_MS = 4000; // 4 sec - - private static final long SCREEN_ON_DELAY_MS = 2000; // 2 sec - - private static final long BIND_SERVICE_TIMEOUT_SEC = 4; - - private static final int REPEAT_TEST_COUNT = 5; - - private static Context mContext; - private static UiDevice mUiDevice; - private static int mTestPkgUid; - private static BatteryManager mBatteryManager; - - private static ServiceConnection mServiceConnection; - private static ICmdReceiverService mCmdReceiverService; - - @BeforeClass - public static void setUpOnce() throws Exception { - mContext = InstrumentationRegistry.getContext(); - mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); - - mContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); - mTestPkgUid = mContext.getPackageManager().getPackageUid(TEST_PKG, 0); - - mBatteryManager = (BatteryManager) mContext.getSystemService(Context.BATTERY_SERVICE); - bindService(); - } - - @AfterClass - public static void tearDownOnce() throws Exception { - batteryReset(); - unbindService(); - } - - private static void bindService() throws Exception { - final CountDownLatch bindLatch = new CountDownLatch(1); - mServiceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - Log.i(TAG, "Service connected"); - mCmdReceiverService = ICmdReceiverService.Stub.asInterface(service); - bindLatch.countDown(); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - Log.i(TAG, "Service disconnected"); - } - }; - final Intent intent = new Intent() - .setComponent(new ComponentName(TEST_PKG, TEST_SERVICE_CLASS)); - // Needs to use BIND_ALLOW_OOM_MANAGEMENT and BIND_NOT_FOREGROUND so that the test app - // does not run in the same process state as this app. - mContext.bindService(intent, mServiceConnection, - Context.BIND_AUTO_CREATE - | Context.BIND_ALLOW_OOM_MANAGEMENT - | Context.BIND_NOT_FOREGROUND); - if (!bindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) { - fail("Timed out waiting for the service to bind in " + mTestPkgUid); - } - } - - private static void unbindService() { - if (mCmdReceiverService != null) { - mContext.unbindService(mServiceConnection); - } - } - - @Test - public void testStartActivity_batterySaver() throws Exception { - setBatterySaverMode(true); - try { - testConnOnActivityStart("testStartActivity_batterySaver"); - } finally { - setBatterySaverMode(false); - } - } - - @Test - public void testStartActivity_dataSaver() throws Exception { - setDataSaverMode(true); - try { - testConnOnActivityStart("testStartActivity_dataSaver"); - } finally { - setDataSaverMode(false); - } - } - - @Test - public void testStartActivity_dozeMode() throws Exception { - setDozeMode(true); - try { - testConnOnActivityStart("testStartActivity_dozeMode"); - } finally { - setDozeMode(false); - } - } - - @Test - public void testStartActivity_appStandby() throws Exception { - try{ - turnBatteryOn(); - setAppIdle(true); - turnScreenOn(); - startActivityAndCheckNetworkAccess(); - } finally { - turnBatteryOff(); - finishActivity(); - setAppIdle(false); - } - } - - @Test - public void testStartActivity_backgroundRestrict() throws Exception { - updateRestrictBackgroundBlacklist(true); - try { - testConnOnActivityStart("testStartActivity_backgroundRestrict"); - } finally { - updateRestrictBackgroundBlacklist(false); - } - } - - private void testConnOnActivityStart(String testName) throws Exception { - for (int i = 1; i <= REPEAT_TEST_COUNT; ++i) { - try { - Log.d(TAG, testName + " Start #" + i); - turnScreenOn(); - startActivityAndCheckNetworkAccess(); - } finally { - finishActivity(); - Log.d(TAG, testName + " end #" + i); - } - } - } - - // TODO: Some of these methods are also used in CTS, so instead of duplicating code, - // create a static library which can be used by both servicestests and cts. - private void setBatterySaverMode(boolean enabled) throws Exception { - if (enabled) { - turnBatteryOn(); - executeCommand("settings put global low_power 1"); - } else { - executeCommand("settings put global low_power 0"); - turnBatteryOff(); - } - final String result = executeCommand("settings get global low_power"); - assertEquals(enabled ? "1" : "0", result); - } - - private void setDataSaverMode(boolean enabled) throws Exception { - executeCommand("cmd netpolicy set restrict-background " + enabled); - final String output = executeCommand("cmd netpolicy get restrict-background"); - final String expectedSuffix = enabled ? "enabled" : "disabled"; - assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'", - output.endsWith(expectedSuffix)); - } - - private void setDozeMode(boolean enabled) throws Exception { - if (enabled) { - turnBatteryOn(); - turnScreenOff(); - executeCommand("dumpsys deviceidle force-idle deep"); - } else { - turnScreenOn(); - turnBatteryOff(); - executeCommand("dumpsys deviceidle unforce"); - } - assertDelayedCommandResult("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE", - 5 /* maxTries */, 500 /* napTimeMs */); - } - - private void setAppIdle(boolean enabled) throws Exception { - executeCommand("am set-inactive " + TEST_PKG + " " + enabled); - assertDelayedCommandResult("am get-inactive " + TEST_PKG, "Idle=" + enabled, - 15 /* maxTries */, 2000 /* napTimeMs */); - } - - private void updateRestrictBackgroundBlacklist(boolean add) throws Exception { - if (add) { - executeCommand("cmd netpolicy add restrict-background-blacklist " + mTestPkgUid); - } else { - executeCommand("cmd netpolicy remove restrict-background-blacklist " + mTestPkgUid); - } - assertRestrictBackground("restrict-background-blacklist", mTestPkgUid, add); - } - - private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception { - final int maxTries = 5; - boolean actual = false; - final String expectedUid = Integer.toString(uid); - String uids = ""; - for (int i = 1; i <= maxTries; i++) { - final String output = executeCommand("cmd netpolicy list " + list); - uids = output.split(":")[1]; - for (String candidate : uids.split(" ")) { - actual = candidate.trim().equals(expectedUid); - if (expected == actual) { - return; - } - } - Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected " - + expected + ", got " + actual + "); sleeping 1s before polling again"); - SystemClock.sleep(1000); - } - fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual - + ". Full list: " + uids); - } - - private void turnBatteryOn() throws Exception { - executeCommand("cmd battery unplug"); - executeCommand("cmd battery set status " + BatteryManager.BATTERY_STATUS_NOT_CHARGING); - assertBatteryOn(); - } - - private void assertBatteryOn() throws Exception { - final long endTime = SystemClock.uptimeMillis() + BATTERY_OFF_TIMEOUT_MS; - while (mBatteryManager.isCharging() && SystemClock.uptimeMillis() < endTime) { - SystemClock.sleep(BATTERY_OFF_CHECK_INTERVAL_MS); - } - assertFalse("Power should be disconnected", mBatteryManager.isCharging()); - } - - private void turnBatteryOff() throws Exception { - executeCommand("cmd battery set ac " + BatteryManager.BATTERY_PLUGGED_AC); - executeCommand("cmd battery set status " + BatteryManager.BATTERY_STATUS_CHARGING); - } - - private static void batteryReset() throws Exception { - executeCommand("cmd battery reset"); - } - - private void turnScreenOff() throws Exception { - executeCommand("input keyevent KEYCODE_SLEEP"); - } - - private void turnScreenOn() throws Exception { - executeCommand("input keyevent KEYCODE_WAKEUP"); - executeCommand("wm dismiss-keyguard"); - // Wait for screen-on state to propagate through the system. - SystemClock.sleep(SCREEN_ON_DELAY_MS); - } - - private static String executeCommand(String cmd) throws IOException { - final String result = executeSilentCommand(cmd); - Log.d(TAG, String.format("Result for '%s': %s", cmd, result)); - return result; - } - - private static String executeSilentCommand(String cmd) throws IOException { - return mUiDevice.executeShellCommand(cmd).trim(); - } - - private void assertDelayedCommandResult(String cmd, String expectedResult, - int maxTries, int napTimeMs) throws Exception { - String result = ""; - for (int i = 1; i <= maxTries; ++i) { - result = executeCommand(cmd); - if (expectedResult.equals(result)) { - return; - } - Log.v(TAG, "Command '" + cmd + "' returned '" + result + " instead of '" - + expectedResult + "' on attempt #" + i - + "; sleeping " + napTimeMs + "ms before trying again"); - SystemClock.sleep(napTimeMs); - } - fail("Command '" + cmd + "' did not return '" + expectedResult + "' after " - + maxTries + " attempts. Last result: '" + result + "'"); - } - - private void startActivityAndCheckNetworkAccess() throws Exception { - final CountDownLatch latch = new CountDownLatch(1); - final Intent launchIntent = new Intent().setComponent( - new ComponentName(TEST_PKG, TEST_ACTIVITY_CLASS)); - final Bundle extras = new Bundle(); - final String[] errors = new String[] {null}; - extras.putBinder(EXTRA_NETWORK_STATE_OBSERVER, new INetworkStateObserver.Stub() { - @Override - public void onNetworkStateChecked(String resultData) { - errors[0] = resultData; - latch.countDown(); - } - }); - launchIntent.putExtras(extras) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(launchIntent); - if (latch.await(NETWORK_CHECK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { - if (errors[0] != null) { - fail("Network not available for test app " + mTestPkgUid + ". " + errors[0]); - } - } else { - fail("Timed out waiting for network availability status from test app " + mTestPkgUid); - } - } - - private static void fail(String msg) throws Exception { - dumpOnFailure(); - Assert.fail(msg); - } - - private static void dumpOnFailure() throws Exception { - dump("network_management"); - dump("netpolicy"); - dumpUsageStats(); - } - - private static void dumpUsageStats() throws Exception { - final String output = executeSilentCommand("dumpsys usagestats"); - final StringBuilder sb = new StringBuilder(); - final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n'); - splitter.setString(output); - String str; - while (splitter.hasNext()) { - str = splitter.next(); - if (str.contains("package=") && !str.contains(TEST_PKG)) { - continue; - } - if (str.trim().startsWith("config=") || str.trim().startsWith("time=")) { - continue; - } - sb.append(str).append('\n'); - } - dump("usagestats", sb.toString()); - } - - private static void dump(String service) throws Exception { - dump(service, executeSilentCommand("dumpsys " + service)); - } - - private static void dump(String service, String dump) throws Exception { - Log.d(TAG, ">>> Begin dump " + service); - Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG, TAG, dump, null); - Log.d(TAG, "<<< End dump " + service); - } - - private void finishActivity() throws Exception { - mCmdReceiverService.finishActivity(); - } -}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index fdf9354747a0..40943774c0af 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -1975,7 +1975,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { if (si == null) { return null; } - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); return new File(si.getBitmapPath()).getName(); } @@ -1984,7 +1984,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { if (si == null) { return null; } - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); return new File(si.getBitmapPath()).getAbsolutePath(); } @@ -2139,7 +2139,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } protected boolean bitmapDirectoryExists(String packageName, int userId) { - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); final File path = new File(mService.getUserBitmapFilePath(userId), packageName); return path.isDirectory(); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 867890f938ba..411b52155abb 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -1040,7 +1040,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { dumpsysOnLogcat(); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); // Check files and directories. // Package 3 has no bitmaps, so we don't create a directory. assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2); @@ -1096,7 +1096,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "3").createNewFile(); makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "4").createNewFile(); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3, "a.b.c", "d.e.f"); @@ -1111,7 +1111,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // The below check is the same as above, except this time USER_0 use the CALLING_PACKAGE_3 // directory. - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3); assertBitmapDirectories(USER_10, CALLING_PACKAGE_1, CALLING_PACKAGE_2); @@ -1390,7 +1390,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .setIcon(Icon.createWithContentUri("test_uri")) .build() ))); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconUri()); @@ -1402,13 +1402,13 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32)) .build() ))); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconResource()); assertEquals(R.drawable.black_32x32, si.getIconResourceId()); }); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); mInjectedCurrentTimeMillis += INTERVAL; // reset throttling @@ -1419,7 +1419,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { getTestContext().getResources(), R.drawable.black_64x64))) .build() ))); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconFile()); @@ -1437,7 +1437,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { getTestContext().getResources(), R.drawable.black_64x64))) .build() ))); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconFile()); @@ -1451,7 +1451,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32)) .build() ))); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconResource()); @@ -1463,7 +1463,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .setIcon(Icon.createWithContentUri("test_uri")) .build() ))); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconUri()); diff --git a/services/tests/servicestests/test-apps/ConnTestApp/Android.bp b/services/tests/servicestests/test-apps/ConnTestApp/Android.bp deleted file mode 100644 index 0731e2ca1f41..000000000000 --- a/services/tests/servicestests/test-apps/ConnTestApp/Android.bp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2017 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test_helper_app { - name: "ConnTestApp", - - test_suites: ["device-tests"], - - static_libs: ["servicestests-aidl"], - srcs: ["**/*.java"], - - platform_apis: true, - certificate: "platform", - dex_preopt: { - enabled: false, - }, - optimize: { - enabled: false, - }, -} diff --git a/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml deleted file mode 100644 index 201cd05052ea..000000000000 --- a/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.servicestests.apps.conntestapp"> - - <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" /> - - <application> - <activity android:name=".ConnTestActivity" - android:exported="true" /> - <service android:name=".CmdReceiverService" - android:exported="true" /> - </application> - -</manifest>
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/ConnTestApp/OWNERS b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS deleted file mode 100644 index aa87958f1d53..000000000000 --- a/services/tests/servicestests/test-apps/ConnTestApp/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /services/core/java/com/android/server/net/OWNERS diff --git a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/CmdReceiverService.java b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/CmdReceiverService.java deleted file mode 100644 index 6130f3a3fc76..000000000000 --- a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/CmdReceiverService.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.servicestests.apps.conntestapp; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; - -import com.android.servicestests.aidl.ICmdReceiverService; - -public class CmdReceiverService extends Service { - private ICmdReceiverService.Stub mBinder = new ICmdReceiverService.Stub() { - @Override - public void finishActivity() { - ConnTestActivity.finishSelf(); - } - }; - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } -}
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java deleted file mode 100644 index f8d1d03cd7a6..000000000000 --- a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.servicestests.apps.conntestapp; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.INetworkPolicyManager; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.INetworkManagementService; -import android.os.Process; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.util.Log; - -import com.android.internal.annotations.GuardedBy; -import com.android.servicestests.aidl.INetworkStateObserver; - -public class ConnTestActivity extends Activity { - private static final String TAG = ConnTestActivity.class.getSimpleName(); - - private static final String TEST_PKG = ConnTestActivity.class.getPackage().getName(); - private static final String ACTION_FINISH_ACTIVITY = TEST_PKG + ".FINISH"; - private static final String EXTRA_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer"; - - private static final Object INSTANCE_LOCK = new Object(); - @GuardedBy("instanceLock") - private static Activity sInstance; - - private BroadcastReceiver finishCommandReceiver = null; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - synchronized (INSTANCE_LOCK) { - sInstance = this; - } - Log.i(TAG, "onCreate called"); - - notifyNetworkStateObserver(); - - finishCommandReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - Log.i(TAG, "finish command received"); - ConnTestActivity.this.finish(); - } - }; - registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY)); - } - - @Override - public void onResume() { - super.onResume(); - Log.i(TAG, "onResume called"); - } - - @Override - public void onStop() { - Log.i(TAG, "onStop called"); - if (finishCommandReceiver != null) { - unregisterReceiver(finishCommandReceiver); - finishCommandReceiver = null; - } - super.onStop(); - } - - @Override - public void finish() { - synchronized (INSTANCE_LOCK) { - sInstance = null; - } - Log.i(TAG, "finish called"); - super.finish(); - } - - public static void finishSelf() { - synchronized (INSTANCE_LOCK) { - if (sInstance != null) { - sInstance.finish(); - } - } - } - - private void notifyNetworkStateObserver() { - if (getIntent() == null) { - return; - } - - final Bundle extras = getIntent().getExtras(); - if (extras == null) { - return; - } - final INetworkStateObserver observer = INetworkStateObserver.Stub.asInterface( - extras.getBinder(EXTRA_NETWORK_STATE_OBSERVER)); - if (observer != null) { - AsyncTask.execute(() -> { - try { - observer.onNetworkStateChecked(checkNetworkStatus()); - } catch (RemoteException e) { - Log.e(TAG, "Error occured while notifying the observer: " + e); - } - }); - } - } - - /** - * Checks whether the network is restricted. - * - * @return null if network is not restricted, otherwise an error message. - */ - private String checkNetworkStatus() { - final INetworkManagementService nms = INetworkManagementService.Stub.asInterface( - ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); - final INetworkPolicyManager npms = INetworkPolicyManager.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); - try { - final boolean restrictedByFwRules = nms.isNetworkRestricted(Process.myUid()); - final boolean restrictedByUidRules = npms.isUidNetworkingBlocked(Process.myUid(), true); - if (restrictedByFwRules || restrictedByUidRules) { - return "Network is restricted by fwRules: " + restrictedByFwRules - + " and uidRules: " + restrictedByUidRules; - } - return null; - } catch (RemoteException e) { - return "Error talking to system server: " + e; - } - } -}
\ No newline at end of file diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index 7689e08bc3f3..2fea2284ff2a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -27,10 +27,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMor import static com.google.common.truth.Truth.assertWithMessage; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; @@ -40,24 +38,22 @@ import android.app.ActivityOptions; import android.app.ActivityOptions.SourceInfo; import android.app.WaitResult; import android.app.WindowConfiguration; +import android.content.ComponentName; import android.content.Intent; import android.os.IBinder; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; +import android.util.Log; import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; -import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; - -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentMatcher; +import org.mockito.ArgumentCaptor; -import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.function.ToIntFunction; @@ -75,13 +71,14 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { private ActivityMetricsLogger mActivityMetricsLogger; private ActivityMetricsLogger.LaunchingState mLaunchingState; private ActivityMetricsLaunchObserver mLaunchObserver; - private ActivityMetricsLaunchObserverRegistry mLaunchObserverRegistry; private ActivityRecord mTrampolineActivity; private ActivityRecord mTopActivity; private ActivityOptions mActivityOptions; private boolean mLaunchTopByTrampoline; private boolean mNewActivityCreated = true; + private long mExpectedStartedId; + private final ArrayMap<ComponentName, Long> mLastLaunchedIds = new ArrayMap<>(); @Before public void setUpAMLO() { @@ -89,9 +86,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // ActivityTaskSupervisor always creates its own instance of ActivityMetricsLogger. mActivityMetricsLogger = mSupervisor.getActivityMetricsLogger(); - - mLaunchObserverRegistry = mActivityMetricsLogger.getLaunchObserverRegistry(); - mLaunchObserverRegistry.registerLaunchObserver(mLaunchObserver); + mActivityMetricsLogger.getLaunchObserverRegistry().registerLaunchObserver(mLaunchObserver); // Sometimes we need an ActivityRecord for ActivityMetricsLogger to do anything useful. // This seems to be the easiest way to create an ActivityRecord. @@ -107,65 +102,70 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { mTrampolineActivity.mVisibleRequested = false; } - @After - public void tearDownAMLO() { - if (mLaunchObserverRegistry != null) { // Don't NPE if setUp failed. - mLaunchObserverRegistry.unregisterLaunchObserver(mLaunchObserver); - } + private <T> T verifyAsync(T mock) { + // With WindowTestRunner, all test methods are inside WM lock, so we have to unblock any + // messages that are waiting for the lock. + waitHandlerIdle(mAtm.mH); + // AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout. + return verify(mock, timeout(TIMEOUT_MS)); } - static class ActivityRecordMatcher implements ArgumentMatcher</*@ActivityRecordProto*/ byte[]> { - private final @ActivityRecordProto byte[] mExpected; - - public ActivityRecordMatcher(ActivityRecord activityRecord) { - mExpected = activityRecordToProto(activityRecord); - } + private void verifyOnActivityLaunched(ActivityRecord activity) { + final ArgumentCaptor<Long> idCaptor = ArgumentCaptor.forClass(Long.class); + verifyAsync(mLaunchObserver).onActivityLaunched(idCaptor.capture(), + eq(activity.mActivityComponent), anyInt()); + final long id = idCaptor.getValue(); + setExpectedStartedId(id, activity); + mLastLaunchedIds.put(activity.mActivityComponent, id); + } - public boolean matches(@ActivityRecordProto byte[] actual) { - return Arrays.equals(mExpected, actual); - } + private void verifyOnActivityLaunchFinished(ActivityRecord activity) { + verifyAsync(mLaunchObserver).onActivityLaunchFinished(eq(mExpectedStartedId), + eq(activity.mActivityComponent), anyLong()); } - static @ActivityRecordProto byte[] activityRecordToProto(ActivityRecord record) { - return ActivityMetricsLogger.convertActivityRecordToProto(record); + private void setExpectedStartedId(long id, Object reason) { + mExpectedStartedId = id; + Log.i("AMLTest", "setExpectedStartedId=" + id + " from " + reason); } - static @ActivityRecordProto byte[] eqProto(ActivityRecord record) { - return argThat(new ActivityRecordMatcher(record)); + private void setLastExpectedStartedId(ActivityRecord r) { + setExpectedStartedId(getLastStartedId(r), r); } - private <T> T verifyAsync(T mock) { - // With WindowTestRunner, all test methods are inside WM lock, so we have to unblock any - // messages that are waiting for the lock. - waitHandlerIdle(mAtm.mH); - // AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout. - return verify(mock, timeout(TIMEOUT_MS)); + private long getLastStartedId(ActivityRecord r) { + final Long id = mLastLaunchedIds.get(r.mActivityComponent); + return id != null ? id : -1; } - private void verifyOnActivityLaunchFinished(ActivityRecord activity) { - verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(activity), anyLong()); + private long eqLastStartedId(ActivityRecord r) { + return eq(getLastStartedId(r)); } - private void onIntentStarted(Intent intent) { + private long onIntentStarted(Intent intent) { notifyActivityLaunching(intent); + long timestamp = -1; // If it is launching top activity from trampoline activity, the observer shouldn't receive // onActivityLaunched because the activities should belong to the same transition. if (!mLaunchTopByTrampoline) { - verifyAsync(mLaunchObserver).onIntentStarted(eq(intent), anyLong()); + final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class); + verifyAsync(mLaunchObserver).onIntentStarted(eq(intent), captor.capture()); + timestamp = captor.getValue(); } verifyNoMoreInteractions(mLaunchObserver); + return timestamp; } @Test public void testOnIntentFailed() { - onIntentStarted(new Intent("testOnIntentFailed")); + final long id = onIntentStarted(new Intent("testOnIntentFailed")); // Bringing an intent that's already running 'to front' is not considered // as an ACTIVITY_LAUNCHED state transition. notifyActivityLaunched(START_TASK_TO_FRONT, null /* launchedActivity */); - verifyAsync(mLaunchObserver).onIntentFailed(); + verifyAsync(mLaunchObserver).onIntentFailed(eq(id)); verifyNoMoreInteractions(mLaunchObserver); } @@ -208,9 +208,8 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { private void onActivityLaunched(ActivityRecord activity) { onIntentStarted(activity.intent); - notifyActivityLaunched(START_SUCCESS, activity); + notifyAndVerifyActivityLaunched(activity); - verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(activity), anyInt()); verifyNoMoreInteractions(mLaunchObserver); } @@ -235,7 +234,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // Cannot time already-visible activities. notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity); - verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTopActivity)); + verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqLastStartedId(mTopActivity)); verifyNoMoreInteractions(mLaunchObserver); } @@ -250,33 +249,33 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { .build(); notifyActivityLaunching(noDrawnActivity.intent); - notifyActivityLaunched(START_SUCCESS, noDrawnActivity); + notifyAndVerifyActivityLaunched(noDrawnActivity); noDrawnActivity.mVisibleRequested = false; mActivityMetricsLogger.notifyVisibilityChanged(noDrawnActivity); - verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(noDrawnActivity)); + verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqLastStartedId(noDrawnActivity)); // If an activity is removed immediately before visibility update, it should cancel too. final ActivityRecord removedImm = new ActivityBuilder(mAtm).setCreateTask(true).build(); clearInvocations(mLaunchObserver); onActivityLaunched(removedImm); removedImm.removeImmediately(); - // Verify any() instead of proto because the field of record may be changed. - verifyAsync(mLaunchObserver).onActivityLaunchCancelled(any()); + verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqLastStartedId(removedImm)); } @Test public void testOnActivityLaunchWhileSleeping() { notifyActivityLaunching(mTrampolineActivity.intent); - notifyActivityLaunched(START_SUCCESS, mTrampolineActivity); + notifyAndVerifyActivityLaunched(mTrampolineActivity); doReturn(true).when(mTrampolineActivity.mDisplayContent).isSleeping(); mTrampolineActivity.setState(ActivityRecord.State.RESUMED, "test"); mTrampolineActivity.setVisibility(false); waitHandlerIdle(mAtm.mH); // Not cancel immediately because in one of real cases, the keyguard may be going away or // occluded later, then the activity can be drawn. - verify(mLaunchObserver, never()).onActivityLaunchCancelled(eqProto(mTrampolineActivity)); + verify(mLaunchObserver, never()).onActivityLaunchCancelled( + eqLastStartedId(mTrampolineActivity)); clearInvocations(mLaunchObserver); mLaunchTopByTrampoline = true; @@ -289,9 +288,8 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // The posted message will acquire wm lock, so the test needs to release the lock to verify. final Throwable error = awaitInWmLock(() -> { try { - // Though the aborting target should be eqProto(mTopActivity), use any() to avoid - // any changes in proto that may cause failure by different arguments. - verify(mLaunchObserver, timeout(TIMEOUT_MS)).onActivityLaunchCancelled(any()); + verify(mLaunchObserver, timeout(TIMEOUT_MS)).onActivityLaunchCancelled( + mExpectedStartedId); } catch (Throwable e) { // Catch any errors including assertion because this runs in another thread. return e; @@ -314,9 +312,8 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { mActivityOptions = ActivityOptions.makeBasic(); mActivityOptions.setSourceInfo(SourceInfo.TYPE_LAUNCHER, SystemClock.uptimeMillis() - 10); onIntentStarted(mTopActivity.intent); - notifyActivityLaunched(START_SUCCESS, mTopActivity); - verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTopActivity), anyInt()); - verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(prev)); + notifyAndVerifyActivityLaunched(mTopActivity); + verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eq(getLastStartedId(prev))); // The activity reports fully drawn before windows drawn, then the fully drawn event will // be pending (see {@link WindowingModeTransitionInfo#pendingFullyDrawn}). @@ -328,7 +325,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { .isEqualTo(SourceInfo.TYPE_LAUNCHER); assertWithMessage("Record event time").that(info.sourceEventDelayMs).isAtLeast(10); - verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mTopActivity), anyLong()); + verifyAsync(mLaunchObserver).onReportFullyDrawn(eq(mExpectedStartedId), anyLong()); verifyOnActivityLaunchFinished(mTopActivity); verifyNoMoreInteractions(mLaunchObserver); @@ -339,9 +336,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { private void onActivityLaunchedTrampoline() { onIntentStarted(mTrampolineActivity.intent); - notifyActivityLaunched(START_SUCCESS, mTrampolineActivity); - - verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTrampolineActivity), anyInt()); + notifyAndVerifyActivityLaunched(mTrampolineActivity); // A second, distinct, activity launch is coalesced into the current app launch sequence. mLaunchTopByTrampoline = true; @@ -370,6 +365,11 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { mNewActivityCreated, activity, mActivityOptions); } + private void notifyAndVerifyActivityLaunched(ActivityRecord activity) { + notifyActivityLaunched(START_SUCCESS, activity); + verifyOnActivityLaunched(activity); + } + private void notifyTransitionStarting(ActivityRecord activity) { final ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>(); reasons.put(activity, ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN); @@ -430,7 +430,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // Cannot time already-visible activities. notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity); - verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTopActivity)); + verifyAsync(mLaunchObserver).onActivityLaunchCancelled(mExpectedStartedId); verifyNoMoreInteractions(mLaunchObserver); } @@ -447,25 +447,14 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // be reported successfully. notifyTransitionStarting(mTopActivity); + verifyOnActivityLaunched(mTopActivity); verifyOnActivityLaunchFinished(mTopActivity); } @Test - public void testActivityRecordProtoIsNotTooBig() { - // The ActivityRecordProto must not be too big, otherwise converting it at runtime - // will become prohibitively expensive. - assertWithMessage("mTopActivity: %s", mTopActivity) - .that(activityRecordToProto(mTopActivity).length) - .isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE); - - assertWithMessage("mTrampolineActivity: %s", mTrampolineActivity) - .that(activityRecordToProto(mTrampolineActivity).length) - .isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE); - } - - @Test public void testConcurrentLaunches() { onActivityLaunched(mTopActivity); + clearInvocations(mLaunchObserver); final ActivityMetricsLogger.LaunchingState previousState = mLaunchingState; final ActivityRecord otherActivity = new ActivityBuilder(mAtm) @@ -476,11 +465,13 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // state should be created here. onActivityLaunched(otherActivity); - assertWithMessage("Different callers should get 2 indepedent launching states") + assertWithMessage("Different callers should get 2 independent launching states") .that(previousState).isNotEqualTo(mLaunchingState); + setLastExpectedStartedId(otherActivity); transitToDrawnAndVerifyOnLaunchFinished(otherActivity); // The first transition should still be valid. + setLastExpectedStartedId(mTopActivity); transitToDrawnAndVerifyOnLaunchFinished(mTopActivity); } @@ -534,10 +525,12 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // Before TopActivity is drawn, it launches another activity on a different display. mActivityMetricsLogger.notifyActivityLaunching(activityOnNewDisplay.intent, mTopActivity /* caller */, mTopActivity.getUid()); - notifyActivityLaunched(START_SUCCESS, activityOnNewDisplay); + notifyAndVerifyActivityLaunched(activityOnNewDisplay); // There should be 2 events instead of coalescing as one event. + setLastExpectedStartedId(mTopActivity); transitToDrawnAndVerifyOnLaunchFinished(mTopActivity); + setLastExpectedStartedId(activityOnNewDisplay); transitToDrawnAndVerifyOnLaunchFinished(activityOnNewDisplay); } @@ -548,9 +541,11 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { onActivityLaunched(mTrampolineActivity); mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent, mTrampolineActivity /* caller */, mTrampolineActivity.getUid()); - notifyActivityLaunched(START_SUCCESS, mTopActivity); + notifyAndVerifyActivityLaunched(mTopActivity); // Different windowing modes should be independent launch events. + setLastExpectedStartedId(mTrampolineActivity); transitToDrawnAndVerifyOnLaunchFinished(mTrampolineActivity); + setLastExpectedStartedId(mTopActivity); transitToDrawnAndVerifyOnLaunchFinished(mTopActivity); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 67f02c7fab55..8474a36dc681 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_CHANGE; @@ -553,6 +554,37 @@ public class AppTransitionControllerTest extends WindowTestsBase { } @Test + public void testGetAnimationTargets_splitScreenOpening() { + // [DisplayContent] - [Task] -+- [split task 1] -+- [Task1] - [AR1] (opening, invisible) + // +- [split task 2] -+- [Task2] - [AR2] (opening, invisible) + final Task singleTopRoot = createTask(mDisplayContent); + final TaskBuilder builder = new TaskBuilder(mSupervisor) + .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW) + .setParentTaskFragment(singleTopRoot) + .setCreatedByOrganizer(true); + final Task splitRoot1 = builder.build(); + final Task splitRoot2 = builder.build(); + splitRoot1.setAdjacentTaskFragment(splitRoot2, false /* moveTogether */); + final ActivityRecord activity1 = createActivityRecordWithParentTask(splitRoot1); + activity1.setVisible(false); + activity1.mVisibleRequested = true; + final ActivityRecord activity2 = createActivityRecordWithParentTask(splitRoot2); + activity2.setVisible(false); + activity2.mVisibleRequested = true; + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + opening.add(activity2); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + + // Promote animation targets up to Task level, not beyond. + assertEquals( + new ArraySet<>(new WindowContainer[]{splitRoot1, splitRoot2}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + } + + @Test public void testGetAnimationTargets_openingClosingTaskFragment() { // [DefaultTDA] - [Task] -+- [TaskFragment1] - [ActivityRecord1] (opening, invisible) // +- [TaskFragment2] - [ActivityRecord2] (closing, visible) diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 32d201fafcfb..263c9364c965 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -41,8 +41,10 @@ import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; @@ -1167,6 +1169,20 @@ public class DisplayContentTests extends WindowTestsBase { assertNull(mDisplayContent.computeImeParent()); } + @UseTestDisplay(addWindows = W_ACTIVITY) + @Test + public void testComputeImeParent_updateParentWhenTargetNotUseIme() throws Exception { + WindowState overlay = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay"); + overlay.setBounds(100, 100, 200, 200); + overlay.mAttrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM; + WindowState app = createWindow(null, TYPE_BASE_APPLICATION, "app"); + mDisplayContent.setImeLayeringTarget(overlay); + mDisplayContent.setImeInputTarget(app); + assertFalse(mDisplayContent.shouldImeAttachedToApp()); + assertEquals(mDisplayContent.getImeContainer().getParentSurfaceControl(), + mDisplayContent.computeImeParent()); + } + @Test public void testInputMethodInputTarget_isClearedWhenWindowStateIsRemoved() throws Exception { final DisplayContent dc = createNewDisplay(); diff --git a/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java index 6e0056821aab..8b0a54050c3c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java @@ -19,7 +19,6 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_PRESENTATION; import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_180; import static com.google.common.truth.Truth.assertThat; @@ -90,8 +89,8 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY); - // An entry for each possible rotation, for a display that can be in a single state. - assertThat(displayInfos.size()).isEqualTo(4); + // An entry for rotation 0, for a display that can be in a single state. + assertThat(displayInfos.size()).isEqualTo(1); assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo); } @@ -100,7 +99,7 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { mPossibleDisplayInfo.add(mDefaultDisplayInfo); mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); - assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(4); + assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(1); // Add another display layout to the set of supported states. mPossibleDisplayInfo.add(mSecondDisplayInfo); @@ -116,12 +115,12 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { defaultDisplayInfos.add(di); } } - // An entry for each possible rotation, for the default display. - assertThat(defaultDisplayInfos).hasSize(4); + // An entry for rotation 0, for the default display. + assertThat(defaultDisplayInfos).hasSize(1); assertPossibleDisplayInfoEntries(defaultDisplayInfos, mDefaultDisplayInfo); - // An entry for each possible rotation, for the second display. - assertThat(secondDisplayInfos).hasSize(4); + // An entry for rotation 0, for the second display. + assertThat(secondDisplayInfos).hasSize(1); assertPossibleDisplayInfoEntries(secondDisplayInfos, mSecondDisplayInfo); } @@ -130,7 +129,7 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { mPossibleDisplayInfo.add(mDefaultDisplayInfo); mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); - assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(4); + assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(1); // Add another display to a different group. mSecondDisplayInfo.displayId = DEFAULT_DISPLAY + 1; @@ -139,14 +138,14 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { mDisplayInfoMapper.updatePossibleDisplayInfos(mSecondDisplayInfo.displayId); Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY); - // An entry for each possible rotation, for the default display. - assertThat(displayInfos).hasSize(4); + // An entry for rotation 0, for the default display. + assertThat(displayInfos).hasSize(1); assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo); Set<DisplayInfo> secondStateEntries = mDisplayInfoMapper.getPossibleDisplayInfos(mSecondDisplayInfo.displayId); - // An entry for each possible rotation, for the second display. - assertThat(secondStateEntries).hasSize(4); + // An entry for rotation 0, for the second display. + assertThat(secondStateEntries).hasSize(1); assertPossibleDisplayInfoEntries(secondStateEntries, mSecondDisplayInfo); } @@ -160,23 +159,10 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { private static void assertPossibleDisplayInfoEntries(Set<DisplayInfo> displayInfos, DisplayInfo expectedDisplayInfo) { - boolean[] seenEveryRotation = new boolean[4]; for (DisplayInfo displayInfo : displayInfos) { - final int rotation = displayInfo.rotation; - seenEveryRotation[rotation] = true; assertThat(displayInfo.displayId).isEqualTo(expectedDisplayInfo.displayId); - assertEqualsRotatedDisplayInfo(displayInfo, expectedDisplayInfo); - } - assertThat(seenEveryRotation).isEqualTo(new boolean[]{true, true, true, true}); - } - - private static void assertEqualsRotatedDisplayInfo(DisplayInfo actual, DisplayInfo expected) { - if (actual.rotation == ROTATION_0 || actual.rotation == ROTATION_180) { - assertThat(actual.logicalWidth).isEqualTo(expected.logicalWidth); - assertThat(actual.logicalHeight).isEqualTo(expected.logicalHeight); - } else { - assertThat(actual.logicalWidth).isEqualTo(expected.logicalHeight); - assertThat(actual.logicalHeight).isEqualTo(expected.logicalWidth); + assertThat(displayInfo.logicalWidth).isEqualTo(expectedDisplayInfo.logicalWidth); + assertThat(displayInfo.logicalHeight).isEqualTo(expectedDisplayInfo.logicalHeight); } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 7507df6eba74..9957d05d0a52 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -1290,6 +1290,7 @@ class WindowTestsBase extends SystemServiceTestsBase { private TaskFragment mParentTaskFragment; private boolean mCreateActivity = false; + private boolean mCreatedByOrganizer = false; TaskBuilder(ActivityTaskSupervisor supervisor) { mSupervisor = supervisor; @@ -1381,6 +1382,11 @@ class WindowTestsBase extends SystemServiceTestsBase { return this; } + TaskBuilder setCreatedByOrganizer(boolean createdByOrganizer) { + mCreatedByOrganizer = createdByOrganizer; + return this; + } + Task build() { SystemServicesTestRule.checkHoldsLock(mSupervisor.mService.mGlobalLock); @@ -1416,7 +1422,8 @@ class WindowTestsBase extends SystemServiceTestsBase { .setActivityInfo(mActivityInfo) .setIntent(mIntent) .setOnTop(mOnTop) - .setVoiceSession(mVoiceSession); + .setVoiceSession(mVoiceSession) + .setCreatedByOrganizer(mCreatedByOrganizer); final Task task; if (mParentTaskFragment == null) { task = builder.setActivityType(mActivityType) diff --git a/telephony/java/Android.bp b/telephony/java/Android.bp index 3941b300206f..76a420c430d1 100644 --- a/telephony/java/Android.bp +++ b/telephony/java/Android.bp @@ -13,6 +13,15 @@ filegroup { srcs: [ "**/*.java", "**/*.aidl", + ":statslog-telephony-java-gen", ], visibility: ["//frameworks/base"], } + +genrule { + name: "statslog-telephony-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module telephony" + + " --javaPackage com.android.internal.telephony --javaClass TelephonyStatsLog", + out: ["com/android/internal/telephony/TelephonyStatsLog.java"], +} diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java index ffdb23f98fb8..f47cf3384791 100644 --- a/telephony/java/android/telephony/AnomalyReporter.java +++ b/telephony/java/android/telephony/AnomalyReporter.java @@ -16,6 +16,8 @@ package android.telephony; +import static com.android.internal.telephony.TelephonyStatsLog.TELEPHONY_ANOMALY_DETECTED; + import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.content.Context; @@ -24,6 +26,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.ParcelUuid; +import com.android.internal.telephony.TelephonyStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.telephony.Rlog; @@ -83,6 +86,12 @@ public final class AnomalyReporter { return; } + TelephonyStatsLog.write( + TELEPHONY_ANOMALY_DETECTED, + 0, // TODO: carrier id needs to be populated + eventId.getLeastSignificantBits(), + eventId.getMostSignificantBits()); + // If this event has already occurred, skip sending intents for it; regardless log its // invocation here. Integer count = sEvents.containsKey(eventId) ? sEvents.get(eventId) + 1 : 1; diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt index 93b987ea8787..aacc17a49a24 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt @@ -16,6 +16,10 @@ package com.android.server.wm.flicker.helpers +import android.view.WindowInsets.Type.ime +import android.view.WindowInsets.Type.navigationBars +import android.view.WindowInsets.Type.statusBars + import android.app.Instrumentation import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice @@ -25,6 +29,8 @@ import com.android.server.wm.traces.common.FlickerComponentName import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import java.util.regex.Pattern + class ImeAppAutoFocusHelper @JvmOverloads constructor( instr: Instrumentation, private val rotation: Int, @@ -72,6 +78,7 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor( wmHelper.waitForAppTransitionIdle() wmHelper.waitForFullScreenApp( ActivityOptions.DIALOG_THEMED_ACTIVITY_COMPONENT_NAME.toFlickerComponent()) + mInstrumentation.waitForIdleSync() } fun dismissDialog(wmHelper: WindowManagerStateHelper) { val dialog = uiDevice.wait( @@ -83,4 +90,27 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor( wmHelper.waitForAppTransitionIdle() } } + fun getInsetsVisibleFromDialog(type: Int): Boolean { + var insetsVisibilityTextView = uiDevice.wait( + Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT) + if (insetsVisibilityTextView != null) { + var visibility = insetsVisibilityTextView.text.toString() + val matcher = when (type) { + ime() -> { + Pattern.compile("IME\\: (VISIBLE|INVISIBLE)").matcher(visibility) + } + statusBars() -> { + Pattern.compile("StatusBar\\: (VISIBLE|INVISIBLE)").matcher(visibility) + } + navigationBars() -> { + Pattern.compile("NavBar\\: (VISIBLE|INVISIBLE)").matcher(visibility) + } + else -> null + } + if (matcher != null && matcher.find()) { + return matcher.group(1).equals("VISIBLE") + } + } + return false + } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt index 1b60403ac354..2f8f9441a7b9 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt @@ -16,6 +16,10 @@ package com.android.server.wm.flicker.ime +import android.view.WindowInsets.Type.ime +import android.view.WindowInsets.Type.navigationBars +import android.view.WindowInsets.Type.statusBars + import android.app.Instrumentation import android.platform.test.annotations.Presubmit import android.view.Surface @@ -35,6 +39,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue /** * Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity. @@ -56,6 +62,10 @@ class LaunchAppShowImeAndDialogThemeAppTest(private val testSpec: FlickerTestPar testApp.launchViaIntent(wmHelper) wmHelper.waitImeShown() testApp.startDialogThemedActivity(wmHelper) + // Verify IME insets isn't visible on dialog since it's non-IME focusable window + assertFalse(testApp.getInsetsVisibleFromDialog(ime())) + assertTrue(testApp.getInsetsVisibleFromDialog(statusBars())) + assertTrue(testApp.getInsetsVisibleFromDialog(navigationBars())) } } teardown { diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java index 27606d81f9d3..20eb295d3e6b 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java @@ -17,11 +17,16 @@ package com.android.server.wm.flicker.testapp; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowInsets.Type.ime; +import static android.view.WindowInsets.Type.navigationBars; +import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import android.app.Activity; import android.app.AlertDialog; +import android.app.Dialog; import android.graphics.Color; import android.os.Bundle; import android.view.WindowManager; @@ -33,9 +38,12 @@ public class DialogThemedActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_simple); + getWindow().addFlags(FLAG_NOT_FOCUSABLE); getWindow().getDecorView().setBackgroundColor(Color.TRANSPARENT); TextView textView = new TextView(this); - textView.setText("This is a test dialog"); + // Print SystemBars' insets visibility on this window for demonstrating during the test. + textView.setId(android.R.id.text1); + textView.setText("Insets visibility\n\n"); textView.setTextColor(Color.BLACK); LinearLayout layout = new LinearLayout(this); layout.setBackgroundColor(Color.GREEN); @@ -51,7 +59,17 @@ public class DialogThemedActivity extends Activity { attrs.flags = FLAG_DIM_BEHIND | FLAG_ALT_FOCUSABLE_IM; dialog.getWindow().getDecorView().setLayoutParams(attrs); dialog.setCanceledOnTouchOutside(true); + dialog.setOnShowListener(d -> textView.setText(textView.getText() + + "IME: " + isInsetsVisible(dialog, ime()) + "\n" + + "StatusBar: " + isInsetsVisible(dialog, statusBars()) + "\n" + + "NavBar: " + isInsetsVisible(dialog, navigationBars()) + "\n") + ); dialog.show(); dialog.setOnDismissListener((d) -> finish()); } + + private String isInsetsVisible(Dialog d, int type) { + return d.getWindow().getDecorView().getRootWindowInsets().isVisible(type) ? "VISIBLE" + : "INVISIBLE"; + } } |