diff options
226 files changed, 5286 insertions, 1968 deletions
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb index 80317e4634f3..748fe14cde3e 100644 --- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb +++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb @@ -1,6 +1,6 @@ drops { android_build_drop { - build_id: "7396576" + build_id: "7464984" target: "CtsShim" source_file: "aosp_arm64/CtsShimPriv.apk" } diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb index 3605b6d0433b..15103b1f175c 100644 --- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb +++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb @@ -1,6 +1,6 @@ drops { android_build_drop { - build_id: "7396576" + build_id: "7464984" target: "CtsShim" source_file: "aosp_arm64/CtsShim.apk" } diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb index 025ec3a9fdaf..2b9ed2fba339 100644 --- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb +++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb @@ -1,6 +1,6 @@ drops { android_build_drop { - build_id: "7396576" + build_id: "7464984" target: "CtsShim" source_file: "aosp_x86_64/CtsShimPriv.apk" } diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb index e19235a12f8f..d4ff3b7336b5 100644 --- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb +++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb @@ -1,6 +1,6 @@ drops { android_build_drop { - build_id: "7396576" + build_id: "7464984" target: "CtsShim" source_file: "aosp_x86_64/CtsShim.apk" } 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 fe0c7f718bb0..fb5129f18417 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -2006,16 +2006,12 @@ public class AlarmManagerService extends SystemService { windowLength = INTERVAL_DAY; } else if ((flags & FLAG_PRIORITIZE) == 0 && windowLength < minAllowedWindow) { // Prioritized alarms are exempt from minimum window limits. - if (CompatChanges.isChangeEnabled( + if (!isExemptFromMinWindowRestrictions(callingUid) && CompatChanges.isChangeEnabled( AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, callingPackage, UserHandle.getUserHandleForUid(callingUid))) { Slog.w(TAG, "Window length " + windowLength + "ms too short; expanding to " + minAllowedWindow + "ms."); windowLength = minAllowedWindow; - } else { - // TODO (b/185199076): Remove temporary log to catch breaking apps. - Slog.wtf(TAG, "Short window " + windowLength + "ms specified by " - + callingPackage); } } maxElapsed = triggerElapsed + windowLength; @@ -2409,6 +2405,13 @@ 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); + } + + /** * Returns true if the given uid does not require SCHEDULE_EXACT_ALARM to set exact, * allow-while-idle alarms. */ diff --git a/api/Android.bp b/api/Android.bp index db1f64c57e2c..a84e6a6cb031 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -69,7 +69,10 @@ genrule { dest: "current.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/public/api", dest: "android.txt", }, @@ -151,7 +154,10 @@ genrule { dest: "removed.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/public/api", dest: "removed.txt", }, @@ -187,7 +193,10 @@ genrule { dest: "system-current.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/system/api", dest: "android.txt", }, @@ -242,7 +251,10 @@ genrule { dest: "system-removed.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/system/api", dest: "removed.txt", }, @@ -279,7 +291,10 @@ genrule { dest: "module-lib-current.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/module-lib/api", dest: "android.txt", }, @@ -336,7 +351,10 @@ genrule { dest: "module-lib-removed.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/module-lib/api", dest: "removed.txt", }, @@ -377,7 +395,10 @@ genrule { dest: "system-server-current.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/system-server/api", dest: "android.txt", }, @@ -401,7 +422,10 @@ genrule { dest: "system-server-removed.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/system-server/api", dest: "removed.txt", }, diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp index 0eff83c99282..a1575173ded6 100644 --- a/cmds/app_process/Android.bp +++ b/cmds/app_process/Android.bp @@ -29,7 +29,16 @@ cc_binary { }, }, - ldflags: ["-Wl,--export-dynamic"], + // Symbols exported from the executable in .dynsym interpose symbols in every + // linker namespace, including an app's classloader namespace. Provide this + // version script to prevent unwanted interposition. + // + // By default, the static linker doesn't export most of an executable's symbols, + // but it will export a symbol that appears to override a symbol in a needed DSO. + // This commonly happens with C++ vaguely-linked entities, such as template + // functions or type_info variables. Hence, a version script is needed even for + // an executable. + version_script: "version-script.txt", shared_libs: [ "libandroid_runtime", diff --git a/cmds/app_process/version-script.txt b/cmds/app_process/version-script.txt new file mode 100644 index 000000000000..a98066a67675 --- /dev/null +++ b/cmds/app_process/version-script.txt @@ -0,0 +1,4 @@ +{ + local: + *; +}; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 42708f9d2e15..80664ed0816f 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3190,6 +3190,7 @@ package android.window { method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.app.ActivityManager.RunningTaskInfo> getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public android.window.WindowContainerToken getImeTarget(int); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.app.ActivityManager.RunningTaskInfo> getRootTasks(int, @NonNull int[]); + method @BinderThread public void onAppSplashScreenViewRemoved(int); method @BinderThread public void onBackPressedOnTaskRoot(@NonNull android.app.ActivityManager.RunningTaskInfo); method @BinderThread public void onTaskAppeared(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.view.SurfaceControl); method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 1ce598b5fa18..8e1f263ebf03 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -419,7 +419,7 @@ public class ActivityOptions { private IBinder mLaunchCookie; private IRemoteTransition mRemoteTransition; private boolean mOverrideTaskTransition; - private int mSplashScreenThemeResId; + private String mSplashScreenThemeResName; @SplashScreen.SplashScreenStyle private int mSplashScreenStyle; private boolean mRemoveWithTaskOrganizer; @@ -1174,7 +1174,7 @@ public class ActivityOptions { mRemoteTransition = IRemoteTransition.Stub.asInterface(opts.getBinder( KEY_REMOTE_TRANSITION)); mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION); - mSplashScreenThemeResId = opts.getInt(KEY_SPLASH_SCREEN_THEME); + mSplashScreenThemeResName = opts.getString(KEY_SPLASH_SCREEN_THEME); mRemoveWithTaskOrganizer = opts.getBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER); mLaunchedFromBubble = opts.getBoolean(KEY_LAUNCHED_FROM_BUBBLE); mTransientLaunch = opts.getBoolean(KEY_TRANSIENT_LAUNCH); @@ -1368,8 +1368,9 @@ public class ActivityOptions { * Gets whether the activity want to be launched as other theme for the splash screen. * @hide */ - public int getSplashScreenThemeResId() { - return mSplashScreenThemeResId; + @Nullable + public String getSplashScreenThemeResName() { + return mSplashScreenThemeResName; } /** @@ -1945,8 +1946,8 @@ public class ActivityOptions { if (mOverrideTaskTransition) { b.putBoolean(KEY_OVERRIDE_TASK_TRANSITION, mOverrideTaskTransition); } - if (mSplashScreenThemeResId != 0) { - b.putInt(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResId); + if (mSplashScreenThemeResName != null && !mSplashScreenThemeResName.isEmpty()) { + b.putString(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResName); } if (mRemoveWithTaskOrganizer) { b.putBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER, mRemoveWithTaskOrganizer); diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index c3272c1a770d..7a806bdf473d 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -45,6 +45,7 @@ public final class AutomaticZenRule implements Parcelable { private long creationTime; private ZenPolicy mZenPolicy; private boolean mModified = false; + private String mPkg; /** * Creates an automatic zen rule. @@ -123,6 +124,7 @@ public final class AutomaticZenRule implements Parcelable { creationTime = source.readLong(); mZenPolicy = source.readParcelable(null); mModified = source.readInt() == ENABLED; + mPkg = source.readString(); } /** @@ -244,6 +246,20 @@ public final class AutomaticZenRule implements Parcelable { this.configurationActivity = componentName; } + /** + * @hide + */ + public void setPackageName(String pkgName) { + mPkg = pkgName; + } + + /** + * @hide + */ + public String getPackageName() { + return mPkg; + } + @Override public int describeContents() { return 0; @@ -265,6 +281,7 @@ public final class AutomaticZenRule implements Parcelable { dest.writeLong(creationTime); dest.writeParcelable(mZenPolicy, 0); dest.writeInt(mModified ? ENABLED : DISABLED); + dest.writeString(mPkg); } @Override @@ -273,6 +290,7 @@ public final class AutomaticZenRule implements Parcelable { .append("enabled=").append(enabled) .append(",name=").append(name) .append(",interruptionFilter=").append(interruptionFilter) + .append(",pkg=").append(mPkg) .append(",conditionId=").append(conditionId) .append(",owner=").append(owner) .append(",configActivity=").append(configurationActivity) @@ -294,13 +312,14 @@ public final class AutomaticZenRule implements Parcelable { && Objects.equals(other.owner, owner) && Objects.equals(other.mZenPolicy, mZenPolicy) && Objects.equals(other.configurationActivity, configurationActivity) + && Objects.equals(other.mPkg, mPkg) && other.creationTime == creationTime; } @Override public int hashCode() { return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, - configurationActivity, mZenPolicy, mModified, creationTime); + configurationActivity, mZenPolicy, mModified, creationTime, mPkg); } public static final @android.annotation.NonNull Parcelable.Creator<AutomaticZenRule> CREATOR diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index f33adb3c01d1..098492c8234b 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -207,7 +207,7 @@ interface INotificationManager void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted); AutomaticZenRule getAutomaticZenRule(String id); List<ZenModeConfig.ZenRule> getZenRules(); - String addAutomaticZenRule(in AutomaticZenRule automaticZenRule); + String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg); boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule); boolean removeAutomaticZenRule(String id); boolean removeAutomaticZenRules(String packageName); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index da03a3da820a..ccf1edb3fecc 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1182,10 +1182,12 @@ public class NotificationManager { List<ZenModeConfig.ZenRule> rules = service.getZenRules(); Map<String, AutomaticZenRule> ruleMap = new HashMap<>(); for (ZenModeConfig.ZenRule rule : rules) { - ruleMap.put(rule.id, new AutomaticZenRule(rule.name, rule.component, + AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component, rule.configurationActivity, rule.conditionId, rule.zenPolicy, zenModeToInterruptionFilter(rule.zenMode), rule.enabled, - rule.creationTime)); + rule.creationTime); + azr.setPackageName(rule.pkg); + ruleMap.put(rule.id, azr); } return ruleMap; } catch (RemoteException e) { @@ -1226,7 +1228,7 @@ public class NotificationManager { public String addAutomaticZenRule(AutomaticZenRule automaticZenRule) { INotificationManager service = getService(); try { - return service.addAutomaticZenRule(automaticZenRule); + return service.addAutomaticZenRule(automaticZenRule, mContext.getPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index e68eb7471e42..0136a35e3975 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -365,7 +365,6 @@ public final class PendingIntent implements Parcelable { } if (Compatibility.isChangeEnabled(PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED) - && !"com.google.android.apps.gcs".equals(packageName) && !flagImmutableSet && !flagMutableSet) { String msg = packageName + ": Targeting S+ (version " + Build.VERSION_CODES.S + " and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE" diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index a9bec98ce405..a0d2977cf09a 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -18,7 +18,6 @@ package android.app.admin; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.app.admin.DevicePolicyManager.OperationSafetyReason; import android.content.ComponentName; import android.content.Intent; import android.os.UserHandle; @@ -256,13 +255,4 @@ public abstract class DevicePolicyManagerInternal { * {@link #supportsResetOp(int)} is true. */ public abstract void resetOp(int op, String packageName, @UserIdInt int userId); - - /** - * Notifies the system that an unsafe operation reason has changed. - * - * @throws IllegalArgumentException if {@code checker} is not the same as set on - * {@code DevicePolicyManagerService}. - */ - public abstract void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker, - @OperationSafetyReason int reason, boolean isSafe); } diff --git a/core/java/android/app/admin/DevicePolicyManagerLiteInternal.java b/core/java/android/app/admin/DevicePolicyManagerLiteInternal.java new file mode 100644 index 000000000000..ccb99470d372 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyManagerLiteInternal.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 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 android.app.admin; + +import android.app.admin.DevicePolicyManager.OperationSafetyReason; + +/** + * Device policy manager local system service interface for methods that don't require the + * {@code device_admin} feature. + * + * Maintenance note: if you need to expose information from DPMS to lower level services such as + * PM/UM/AM/etc, then exposing it from DevicePolicyManagerInternal is not safe because it may cause + * lock order inversion. Consider using {@link DevicePolicyCache} instead. + * + * @hide Only for use within the system server. + */ +public interface DevicePolicyManagerLiteInternal { + + /** + * Notifies the system that an unsafe operation reason has changed. + * + * @throws IllegalArgumentException if {@code checker} is not the same as set on + * {@code DevicePolicyManagerService.setDevicePolicySafetyChecker()}. + */ + void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker, + @OperationSafetyReason int reason, boolean isSafe); +} diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java index 63f93bfa24e5..806091e2158d 100644 --- a/core/java/android/content/pm/AppSearchShortcutInfo.java +++ b/core/java/android/content/pm/AppSearchShortcutInfo.java @@ -423,7 +423,7 @@ public class AppSearchShortcutInfo extends GenericDocument { shortLabelResName, longLabel, longLabelResId, longLabelResName, disabledMessage, disabledMessageResId, disabledMessageResName, categoriesSet, intents, rank, extras, getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri, - disabledReason, persons, locusId, 0); + disabledReason, persons, locusId, null); si.setImplicitRank(implicitRank); if ((implicitRank & ShortcutInfo.RANK_CHANGED_BIT) != 0) { si.setRankChanged(); diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 76712b5ce2dc..a264bebb5d88 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -449,7 +449,7 @@ public final class ShortcutInfo implements Parcelable { private int mDisabledReason; - private int mStartingThemeResId; + @Nullable private String mStartingThemeResName; private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); @@ -478,8 +478,9 @@ public final class ShortcutInfo implements Parcelable { mExtras = b.mExtras; mLocusId = b.mLocusId; + mStartingThemeResName = b.mStartingThemeResId != 0 + ? b.mContext.getResources().getResourceName(b.mStartingThemeResId) : null; updateTimestamp(); - mStartingThemeResId = b.mStartingThemeResId; } /** @@ -626,7 +627,7 @@ public final class ShortcutInfo implements Parcelable { // Set this bit. mFlags |= FLAG_KEY_FIELDS_ONLY; } - mStartingThemeResId = source.mStartingThemeResId; + mStartingThemeResName = source.mStartingThemeResName; } /** @@ -950,8 +951,8 @@ public final class ShortcutInfo implements Parcelable { if (source.mLocusId != null) { mLocusId = source.mLocusId; } - if (source.mStartingThemeResId != 0) { - mStartingThemeResId = source.mStartingThemeResId; + if (source.mStartingThemeResName != null && !source.mStartingThemeResName.isEmpty()) { + mStartingThemeResName = source.mStartingThemeResName; } } @@ -1454,11 +1455,12 @@ public final class ShortcutInfo implements Parcelable { } /** - * Returns the theme resource id used for the splash screen. + * Returns the theme resource name used for the splash screen. * @hide */ - public int getStartingThemeResId() { - return mStartingThemeResId; + @Nullable + public String getStartingThemeResName() { + return mStartingThemeResName; } /** @hide -- old signature, the internal code still uses it. */ @@ -2182,7 +2184,7 @@ public final class ShortcutInfo implements Parcelable { mPersons = source.readParcelableArray(cl, Person.class); mLocusId = source.readParcelable(cl); mIconUri = source.readString8(); - mStartingThemeResId = source.readInt(); + mStartingThemeResName = source.readString8(); } @Override @@ -2234,7 +2236,7 @@ public final class ShortcutInfo implements Parcelable { dest.writeParcelableArray(mPersons, flags); dest.writeParcelable(mLocusId, flags); dest.writeString8(mIconUri); - dest.writeInt(mStartingThemeResId); + dest.writeString8(mStartingThemeResName); } public static final @NonNull Creator<ShortcutInfo> CREATOR = @@ -2391,10 +2393,10 @@ public final class ShortcutInfo implements Parcelable { sb.append("disabledReason="); sb.append(getDisabledReasonDebugString(mDisabledReason)); - if (mStartingThemeResId != 0) { + if (mStartingThemeResName != null && !mStartingThemeResName.isEmpty()) { addIndentOrComma(sb, indent); - sb.append("SplashScreenThemeResId="); - sb.append(Integer.toHexString(mStartingThemeResId)); + sb.append("SplashScreenThemeResName="); + sb.append(mStartingThemeResName); } addIndentOrComma(sb, indent); @@ -2482,7 +2484,8 @@ public final class ShortcutInfo implements Parcelable { Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras, long lastChangedTimestamp, int flags, int iconResId, String iconResName, String bitmapPath, String iconUri, - int disabledReason, Person[] persons, LocusId locusId, int startingThemeResId) { + int disabledReason, Person[] persons, LocusId locusId, + @Nullable String startingThemeResName) { mUserId = userId; mId = id; mPackageName = packageName; @@ -2511,6 +2514,6 @@ public final class ShortcutInfo implements Parcelable { mDisabledReason = disabledReason; mPersons = persons; mLocusId = locusId; - mStartingThemeResId = startingThemeResId; + mStartingThemeResName = startingThemeResName; } } diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java index 233abf36131b..3ed5c6457fa5 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -74,7 +74,7 @@ public abstract class ShortcutServiceInternal { /** * Get the theme res ID of the starting window, it can be 0 if not specified. */ - public abstract int getShortcutStartingThemeResId(int launcherUserId, + public abstract @Nullable String getShortcutStartingThemeResName(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId); diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java index a0027077e1a3..60a365835e2e 100644 --- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java +++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java @@ -63,6 +63,11 @@ public interface BiometricAuthenticator { */ int TYPE_FACE = 1 << 3; + /** + * @hide + */ + int TYPE_ANY_BIOMETRIC = TYPE_FINGERPRINT | TYPE_IRIS | TYPE_FACE; + @IntDef(flag = true, value = { TYPE_NONE, TYPE_CREDENTIAL, diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java index ee86265e81ad..802387c675b5 100644 --- a/core/java/android/os/AggregateBatteryConsumer.java +++ b/core/java/android/os/AggregateBatteryConsumer.java @@ -17,7 +17,13 @@ package android.os; import android.annotation.NonNull; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.io.PrintWriter; /** @@ -72,6 +78,43 @@ public final class AggregateBatteryConsumer extends BatteryConsumer implements P return mConsumedPowerMah; } + /** Serializes this object to XML */ + void writeToXml(TypedXmlSerializer serializer, + @BatteryUsageStats.AggregateBatteryConsumerScope int scope) throws IOException { + serializer.startTag(null, BatteryUsageStats.XML_TAG_AGGREGATE); + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_SCOPE, scope); + serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, mConsumedPowerMah); + mPowerComponents.writeToXml(serializer); + serializer.endTag(null, BatteryUsageStats.XML_TAG_AGGREGATE); + } + + /** Parses an XML representation and populates the BatteryUsageStats builder */ + static void parseXml(TypedXmlPullParser parser, BatteryUsageStats.Builder builder) + throws XmlPullParserException, IOException { + final int scope = parser.getAttributeInt(null, BatteryUsageStats.XML_ATTR_SCOPE); + final Builder consumerBuilder = builder.getAggregateBatteryConsumerBuilder(scope); + + int eventType = parser.getEventType(); + if (eventType != XmlPullParser.START_TAG || !parser.getName().equals( + BatteryUsageStats.XML_TAG_AGGREGATE)) { + throw new XmlPullParserException("Invalid XML parser state"); + } + + consumerBuilder.setConsumedPower( + parser.getAttributeDouble(null, BatteryUsageStats.XML_ATTR_POWER)); + + while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals( + BatteryUsageStats.XML_TAG_AGGREGATE)) + && eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals(BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) { + PowerComponents.parseXml(parser, consumerBuilder.mPowerComponentsBuilder); + } + } + eventType = parser.next(); + } + } + /** * Builder for DeviceBatteryConsumer. */ @@ -91,6 +134,14 @@ public final class AggregateBatteryConsumer extends BatteryConsumer implements P } /** + * Adds power and usage duration from the supplied AggregateBatteryConsumer. + */ + public void add(AggregateBatteryConsumer aggregateBatteryConsumer) { + mConsumedPowerMah += aggregateBatteryConsumer.mConsumedPowerMah; + mPowerComponentsBuilder.addPowerAndDuration(aggregateBatteryConsumer.mPowerComponents); + } + + /** * Creates a read-only object out of the Builder values. */ @NonNull diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index 6c9f0f677db9..77f8a87cdcd1 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -20,16 +20,23 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.util.Range; import android.util.SparseArray; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; import android.util.proto.ProtoOutputStream; import com.android.internal.os.BatteryStatsHistory; import com.android.internal.os.BatteryStatsHistoryIterator; import com.android.internal.os.PowerCalculator; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.List; @@ -77,6 +84,34 @@ public final class BatteryUsageStats implements Parcelable { private static final int STATSD_PULL_ATOM_MAX_BYTES = 45000; + // XML tags and attributes for BatteryUsageStats persistence + static final String XML_TAG_BATTERY_USAGE_STATS = "battery_usage_stats"; + static final String XML_TAG_AGGREGATE = "aggregate"; + static final String XML_TAG_UID = "uid"; + static final String XML_TAG_USER = "user"; + static final String XML_TAG_POWER_COMPONENTS = "power_components"; + static final String XML_TAG_COMPONENT = "component"; + static final String XML_TAG_CUSTOM_COMPONENT = "custom_component"; + static final String XML_ATTR_ID = "id"; + static final String XML_ATTR_UID = "uid"; + static final String XML_ATTR_USER_ID = "user_id"; + static final String XML_ATTR_SCOPE = "scope"; + static final String XML_ATTR_PREFIX_CUSTOM_COMPONENT = "custom_component_"; + static final String XML_ATTR_START_TIMESTAMP = "start_timestamp"; + static final String XML_ATTR_END_TIMESTAMP = "end_timestamp"; + static final String XML_ATTR_POWER = "power"; + static final String XML_ATTR_DURATION = "duration"; + static final String XML_ATTR_MODEL = "model"; + static final String XML_ATTR_BATTERY_CAPACITY = "battery_capacity"; + static final String XML_ATTR_DISCHARGE_PERCENT = "discharge_pct"; + static final String XML_ATTR_DISCHARGE_LOWER = "discharge_lower"; + static final String XML_ATTR_DISCHARGE_UPPER = "discharge_upper"; + static final String XML_ATTR_BATTERY_REMAINING = "battery_remaining"; + static final String XML_ATTR_CHARGE_REMAINING = "charge_remaining"; + static final String XML_ATTR_HIGHEST_DRAIN_PACKAGE = "highest_drain_package"; + static final String XML_ATTR_TIME_IN_FOREGROUND = "time_in_foreground"; + static final String XML_ATTR_TIME_IN_BACKGROUND = "time_in_background"; + private final int mDischargePercentage; private final double mBatteryCapacityMah; private final long mStatsStartTimestampMs; @@ -96,11 +131,7 @@ public final class BatteryUsageStats implements Parcelable { private BatteryUsageStats(@NonNull Builder builder) { mStatsStartTimestampMs = builder.mStatsStartTimestampMs; mStatsEndTimestampMs = builder.mStatsEndTimestampMs; - if (builder.mStatsDurationMs != -1) { - mStatsDurationMs = builder.mStatsDurationMs; - } else { - mStatsDurationMs = mStatsEndTimestampMs - mStatsStartTimestampMs; - } + mStatsDurationMs = builder.getStatsDuration(); mBatteryCapacityMah = builder.mBatteryCapacityMah; mDischargePercentage = builder.mDischargePercentage; mDischargedPowerLowerBound = builder.mDischargedPowerLowerBoundMah; @@ -579,6 +610,109 @@ public final class BatteryUsageStats implements Parcelable { } } + /** Serializes this object to XML */ + public void writeXml(TypedXmlSerializer serializer) throws IOException { + serializer.startTag(null, XML_TAG_BATTERY_USAGE_STATS); + + for (int i = 0; i < mCustomPowerComponentNames.length; i++) { + serializer.attribute(null, XML_ATTR_PREFIX_CUSTOM_COMPONENT + i, + mCustomPowerComponentNames[i]); + } + + serializer.attributeLong(null, XML_ATTR_START_TIMESTAMP, mStatsStartTimestampMs); + serializer.attributeLong(null, XML_ATTR_END_TIMESTAMP, mStatsEndTimestampMs); + serializer.attributeLong(null, XML_ATTR_DURATION, mStatsDurationMs); + serializer.attributeDouble(null, XML_ATTR_BATTERY_CAPACITY, mBatteryCapacityMah); + serializer.attributeInt(null, XML_ATTR_DISCHARGE_PERCENT, mDischargePercentage); + serializer.attributeDouble(null, XML_ATTR_DISCHARGE_LOWER, mDischargedPowerLowerBound); + serializer.attributeDouble(null, XML_ATTR_DISCHARGE_UPPER, mDischargedPowerUpperBound); + serializer.attributeLong(null, XML_ATTR_BATTERY_REMAINING, mBatteryTimeRemainingMs); + serializer.attributeLong(null, XML_ATTR_CHARGE_REMAINING, mChargeTimeRemainingMs); + + for (int scope = 0; scope < BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; + scope++) { + mAggregateBatteryConsumers[scope].writeToXml(serializer, scope); + } + for (UidBatteryConsumer consumer : mUidBatteryConsumers) { + consumer.writeToXml(serializer); + } + for (UserBatteryConsumer consumer : mUserBatteryConsumers) { + consumer.writeToXml(serializer); + } + serializer.endTag(null, XML_TAG_BATTERY_USAGE_STATS); + } + + /** Parses an XML representation of BatteryUsageStats */ + public static BatteryUsageStats createFromXml(TypedXmlPullParser parser) + throws XmlPullParserException, IOException { + Builder builder = null; + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG + && parser.getName().equals(XML_TAG_BATTERY_USAGE_STATS)) { + List<String> customComponentNames = new ArrayList<>(); + int i = 0; + while (true) { + int index = parser.getAttributeIndex(null, + XML_ATTR_PREFIX_CUSTOM_COMPONENT + i); + if (index == -1) { + break; + } + customComponentNames.add(parser.getAttributeValue(index)); + i++; + } + + builder = new Builder( + customComponentNames.toArray(new String[0]), true); + + builder.setStatsStartTimestamp( + parser.getAttributeLong(null, XML_ATTR_START_TIMESTAMP)); + builder.setStatsEndTimestamp( + parser.getAttributeLong(null, XML_ATTR_END_TIMESTAMP)); + builder.setStatsDuration( + parser.getAttributeLong(null, XML_ATTR_DURATION)); + builder.setBatteryCapacity( + parser.getAttributeDouble(null, XML_ATTR_BATTERY_CAPACITY)); + builder.setDischargePercentage( + parser.getAttributeInt(null, XML_ATTR_DISCHARGE_PERCENT)); + builder.setDischargedPowerRange( + parser.getAttributeDouble(null, XML_ATTR_DISCHARGE_LOWER), + parser.getAttributeDouble(null, XML_ATTR_DISCHARGE_UPPER)); + builder.setBatteryTimeRemainingMs( + parser.getAttributeLong(null, XML_ATTR_BATTERY_REMAINING)); + builder.setChargeTimeRemainingMs( + parser.getAttributeLong(null, XML_ATTR_CHARGE_REMAINING)); + + eventType = parser.next(); + break; + } + eventType = parser.next(); + } + + if (builder == null) { + throw new XmlPullParserException("No root element"); + } + + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + switch (parser.getName()) { + case XML_TAG_AGGREGATE: + AggregateBatteryConsumer.parseXml(parser, builder); + break; + case XML_TAG_UID: + UidBatteryConsumer.createFromXml(parser, builder); + break; + case XML_TAG_USER: + UserBatteryConsumer.createFromXml(parser, builder); + break; + } + } + eventType = parser.next(); + } + + return builder.build(); + } + /** * Builder for BatteryUsageStats. */ @@ -658,6 +792,14 @@ public final class BatteryUsageStats implements Parcelable { return this; } + private long getStatsDuration() { + if (mStatsDurationMs != -1) { + return mStatsDurationMs; + } else { + return mStatsEndTimestampMs - mStatsStartTimestampMs; + } + } + /** * Sets the battery discharge amount since BatteryStats reset as percentage of the full * charge. @@ -738,6 +880,22 @@ public final class BatteryUsageStats implements Parcelable { } /** + * Creates or returns a UidBatteryConsumer, which represents battery attribution + * data for an individual UID. This version of the method is not suitable for use + * with PowerCalculators. + */ + @NonNull + public UidBatteryConsumer.Builder getOrCreateUidBatteryConsumerBuilder(int uid) { + UidBatteryConsumer.Builder builder = mUidBatteryConsumerBuilders.get(uid); + if (builder == null) { + builder = new UidBatteryConsumer.Builder(mCustomPowerComponentNames, + mIncludePowerModels, uid); + mUidBatteryConsumerBuilders.put(uid, builder); + } + return builder; + } + + /** * Creates or returns a UserBatteryConsumer, which represents battery attribution * data for an individual {@link UserHandle}. */ @@ -756,5 +914,59 @@ public final class BatteryUsageStats implements Parcelable { public SparseArray<UidBatteryConsumer.Builder> getUidBatteryConsumerBuilders() { return mUidBatteryConsumerBuilders; } + + /** + * Adds battery usage stats from another snapshots. The two snapshots are assumed to be + * non-overlapping, meaning that the power consumption estimates and session durations + * can be simply summed across the two snapshots. This remains true even if the timestamps + * seem to indicate that the sessions are in fact overlapping: timestamps may be off as a + * result of realtime clock adjustments by the user or the system. + */ + @NonNull + public Builder add(BatteryUsageStats stats) { + if (!Arrays.equals(mCustomPowerComponentNames, stats.mCustomPowerComponentNames)) { + throw new IllegalArgumentException( + "BatteryUsageStats have different custom power components"); + } + + if (mUserBatteryConsumerBuilders.size() != 0 + || !stats.getUserBatteryConsumers().isEmpty()) { + throw new UnsupportedOperationException( + "Combining UserBatteryConsumers is not supported"); + } + + mDischargedPowerLowerBoundMah += stats.mDischargedPowerLowerBound; + mDischargedPowerUpperBoundMah += stats.mDischargedPowerUpperBound; + mDischargePercentage += stats.mDischargePercentage; + + mStatsDurationMs = getStatsDuration() + stats.getStatsDuration(); + + if (mStatsStartTimestampMs == 0 + || stats.mStatsStartTimestampMs < mStatsStartTimestampMs) { + mStatsStartTimestampMs = stats.mStatsStartTimestampMs; + } + + final boolean addingLaterSnapshot = stats.mStatsEndTimestampMs > mStatsEndTimestampMs; + if (addingLaterSnapshot) { + mStatsEndTimestampMs = stats.mStatsEndTimestampMs; + } + + for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) { + getAggregateBatteryConsumerBuilder(scope) + .add(stats.mAggregateBatteryConsumers[scope]); + } + + for (UidBatteryConsumer consumer : stats.getUidBatteryConsumers()) { + getOrCreateUidBatteryConsumerBuilder(consumer.getUid()).add(consumer); + } + + if (addingLaterSnapshot) { + mBatteryCapacityMah = stats.mBatteryCapacityMah; + mBatteryTimeRemainingMs = stats.mBatteryTimeRemainingMs; + mChargeTimeRemainingMs = stats.mChargeTimeRemainingMs; + } + + return this; + } } } diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java index 50804422e92f..97f24ccecaee 100644 --- a/core/java/android/os/BatteryUsageStatsQuery.java +++ b/core/java/android/os/BatteryUsageStatsQuery.java @@ -72,12 +72,16 @@ public final class BatteryUsageStatsQuery implements Parcelable { @NonNull private final int[] mUserIds; private final long mMaxStatsAgeMs; + private long mFromTimestamp; + private long mToTimestamp; private BatteryUsageStatsQuery(@NonNull Builder builder) { mFlags = builder.mFlags; mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray() : new int[]{UserHandle.USER_ALL}; mMaxStatsAgeMs = builder.mMaxStatsAgeMs; + mFromTimestamp = builder.mFromTimestamp; + mToTimestamp = builder.mToTimestamp; } @BatteryUsageStatsFlags @@ -112,11 +116,30 @@ public final class BatteryUsageStatsQuery implements Parcelable { return mMaxStatsAgeMs; } + /** + * Returns the exclusive lower bound of the stored snapshot timestamps that should be included + * in the aggregation. Ignored if {@link #getToTimestamp()} is zero. + */ + public long getFromTimestamp() { + return mFromTimestamp; + } + + /** + * Returns the inclusive upper bound of the stored snapshot timestamps that should + * be included in the aggregation. The default is to include only the current stats + * accumulated since the latest battery reset. + */ + public long getToTimestamp() { + return mToTimestamp; + } + private BatteryUsageStatsQuery(Parcel in) { mFlags = in.readInt(); mUserIds = new int[in.readInt()]; in.readIntArray(mUserIds); mMaxStatsAgeMs = in.readLong(); + mFromTimestamp = in.readLong(); + mToTimestamp = in.readLong(); } @Override @@ -125,6 +148,8 @@ public final class BatteryUsageStatsQuery implements Parcelable { dest.writeInt(mUserIds.length); dest.writeIntArray(mUserIds); dest.writeLong(mMaxStatsAgeMs); + dest.writeLong(mFromTimestamp); + dest.writeLong(mToTimestamp); } @Override @@ -153,6 +178,8 @@ public final class BatteryUsageStatsQuery implements Parcelable { private int mFlags; private IntArray mUserIds; private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS; + private long mFromTimestamp; + private long mToTimestamp; /** * Builds a read-only BatteryUsageStatsQuery object. @@ -204,6 +231,17 @@ public final class BatteryUsageStatsQuery implements Parcelable { } /** + * Requests to aggregate stored snapshots between the two supplied timestamps + * @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis() + * @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis() + */ + public Builder aggregateSnapshots(long fromTimestamp, long toTimestamp) { + mFromTimestamp = fromTimestamp; + mToTimestamp = toTimestamp; + return this; + } + + /** * Set the client's tolerance for stale battery stats. The data may be up to * this many milliseconds out-of-date. */ diff --git a/core/java/android/os/PackageTagsList.java b/core/java/android/os/PackageTagsList.java index c94d3de33b6f..4a81dc6f592e 100644 --- a/core/java/android/os/PackageTagsList.java +++ b/core/java/android/os/PackageTagsList.java @@ -23,20 +23,26 @@ import android.annotation.TestApi; import android.util.ArrayMap; import android.util.ArraySet; +import com.android.internal.annotations.Immutable; + import java.io.PrintWriter; +import java.util.Collection; import java.util.Map; import java.util.Objects; import java.util.Set; /** - * A list of packages and associated attribution tags that supports easy membership checks. + * A list of packages and associated attribution tags that supports easy membership checks. Supports + * "wildcard" attribution tags (ie, matching any attribution tag under a package) in additional to + * standard checks. * * @hide */ @TestApi +@Immutable public final class PackageTagsList implements Parcelable { - // an empty set value matches any attribution tag + // an empty set value matches any attribution tag (ie, wildcard) private final ArrayMap<String, ArraySet<String>> mPackageTags; private PackageTagsList(@NonNull ArrayMap<String, ArraySet<String>> packageTags) { @@ -51,15 +57,34 @@ public final class PackageTagsList implements Parcelable { } /** - * Returns true if the given package is represented within this instance. If this returns true - * this does not imply anything about whether any given attribution tag under the given package - * name is present. + * Returns true if the given package is found within this instance. If this returns true this + * does not imply anything about whether any given attribution tag under the given package name + * is present. */ public boolean includes(@NonNull String packageName) { return mPackageTags.containsKey(packageName); } /** + * Returns true if the given attribution tag is found within this instance under any package. + * Only returns true if the attribution tag literal is found, not if any package contains the + * set of all attribution tags. + * + * @hide + */ + public boolean includesTag(@NonNull String attributionTag) { + final int size = mPackageTags.size(); + for (int i = 0; i < size; i++) { + ArraySet<String> tags = mPackageTags.valueAt(i); + if (tags.contains(attributionTag)) { + return true; + } + } + + return false; + } + + /** * Returns true if all attribution tags under the given package are contained within this * instance. */ @@ -76,6 +101,7 @@ public final class PackageTagsList implements Parcelable { if (tags == null) { return false; } else if (tags.isEmpty()) { + // our tags are the full set, so we contain any attribution tag return true; } else { return tags.contains(attributionTag); @@ -98,10 +124,12 @@ public final class PackageTagsList implements Parcelable { return false; } if (tags.isEmpty()) { + // our tags are the full set, so we contain whatever the other tags are continue; } ArraySet<String> otherTags = packageTagsList.mPackageTags.valueAt(i); if (otherTags.isEmpty()) { + // other tags are the full set, so we can't contain them return false; } if (!tags.containsAll(otherTags)) { @@ -248,6 +276,31 @@ public final class PackageTagsList implements Parcelable { } /** + * Adds the specified package and set of attribution tags to the builder. + * + * @hide + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder add(@NonNull String packageName, + @NonNull Collection<String> attributionTags) { + if (attributionTags.isEmpty()) { + // the input is not allowed to specify a full set by passing in an empty collection + return this; + } + + ArraySet<String> tags = mPackageTags.get(packageName); + if (tags == null) { + tags = new ArraySet<>(attributionTags); + mPackageTags.put(packageName, tags); + } else if (!tags.isEmpty()) { + // if we contain the full set, already done, otherwise add all the tags + tags.addAll(attributionTags); + } + + return this; + } + + /** * Adds the specified {@link PackageTagsList} to the builder. */ @SuppressLint("MissingGetterMatchingBuilder") @@ -267,13 +320,92 @@ public final class PackageTagsList implements Parcelable { if (newTags.isEmpty()) { add(entry.getKey()); } else { - ArraySet<String> tags = mPackageTags.get(entry.getKey()); - if (tags == null) { - tags = new ArraySet<>(newTags); - mPackageTags.put(entry.getKey(), tags); - } else if (!tags.isEmpty()) { - tags.addAll(newTags); - } + add(entry.getKey(), newTags); + } + } + + return this; + } + + /** + * Removes all attribution tags under the specified package from the builder. + * + * @hide + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder remove(@NonNull String packageName) { + mPackageTags.remove(packageName); + return this; + } + + /** + * Removes the specified package and attribution tag from the builder if and only if the + * specified attribution tag is listed explicitly under the package. If the package contains + * all possible attribution tags, then nothing will be removed. + * + * @hide + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder remove(@NonNull String packageName, + @Nullable String attributionTag) { + ArraySet<String> tags = mPackageTags.get(packageName); + if (tags != null && tags.remove(attributionTag) && tags.isEmpty()) { + mPackageTags.remove(packageName); + } + return this; + } + + /** + * Removes the specified package and set of attribution tags from the builder if and only if + * the specified set of attribution tags are listed explicitly under the package. If the + * package contains all possible attribution tags, then nothing will be removed. + * + * @hide + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder remove(@NonNull String packageName, + @NonNull Collection<String> attributionTags) { + if (attributionTags.isEmpty()) { + // the input is not allowed to specify a full set by passing in an empty collection + return this; + } + + ArraySet<String> tags = mPackageTags.get(packageName); + if (tags != null && tags.removeAll(attributionTags) && tags.isEmpty()) { + mPackageTags.remove(packageName); + } + return this; + } + + /** + * Removes the specified {@link PackageTagsList} from the builder. If a package contains all + * possible attribution tags, it will only be removed if the package in the removed + * {@link PackageTagsList} also contains all possible attribution tags. + * + * @hide + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder remove(@NonNull PackageTagsList packageTagsList) { + return remove(packageTagsList.mPackageTags); + } + + /** + * Removes the given map of package to attribution tags to the builder. An empty set of + * attribution tags is interpreted to imply all attribution tags under that package. If a + * package contains all possible attribution tags, it will only be removed if the package in + * the removed map also contains all possible attribution tags. + * + * @hide + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder remove(@NonNull Map<String, ? extends Set<String>> packageTagsMap) { + for (Map.Entry<String, ? extends Set<String>> entry : packageTagsMap.entrySet()) { + Set<String> removedTags = entry.getValue(); + if (removedTags.isEmpty()) { + // if removing the full set, drop the package completely + remove(entry.getKey()); + } else { + remove(entry.getKey(), removedTags); } } diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java index a90ed20d54fc..db3d13bdb07b 100644 --- a/core/java/android/os/PowerComponents.java +++ b/core/java/android/os/PowerComponents.java @@ -19,11 +19,18 @@ import static android.os.BatteryConsumer.convertMahToDeciCoulombs; import android.annotation.NonNull; import android.annotation.Nullable; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; import android.util.proto.ProtoOutputStream; import com.android.internal.os.PowerCalculator; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.io.PrintWriter; +import java.util.Arrays; /** * Contains details of battery attribution data broken down to individual power drain types @@ -36,9 +43,12 @@ class PowerComponents { - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; private final double mConsumedPowerMah; + @NonNull private final double[] mPowerComponentsMah; + @NonNull private final long[] mUsageDurationsMs; private final int mCustomPowerComponentCount; + @Nullable private final byte[] mPowerModels; // Not written to Parcel and must be explicitly restored during the parent object's unparceling private String[] mCustomPowerComponentNames; @@ -49,7 +59,7 @@ class PowerComponents { mPowerComponentsMah = builder.mPowerComponentsMah; mUsageDurationsMs = builder.mUsageDurationsMs; mConsumedPowerMah = builder.getTotalPower(); - mPowerModels = builder.mPowerModels; + mPowerModels = builder.getPowerModels(); } PowerComponents(@NonNull Parcel source) { @@ -146,9 +156,13 @@ class PowerComponents { } } + public boolean hasPowerModels() { + return mPowerModels != null; + } + @BatteryConsumer.PowerModel int getPowerModel(@BatteryConsumer.PowerComponent int component) { - if (mPowerModels == null) { + if (!hasPowerModels()) { throw new IllegalStateException( "Power model IDs were not requested in the BatteryUsageStatsQuery"); } @@ -294,10 +308,128 @@ class PowerComponents { return interestingData; } + void writeToXml(TypedXmlSerializer serializer) throws IOException { + serializer.startTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS); + for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; + componentId++) { + final double powerMah = getConsumedPower(componentId); + final long durationMs = getUsageDurationMillis(componentId); + if (powerMah == 0 && durationMs == 0) { + continue; + } + + serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT); + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId); + if (powerMah != 0) { + serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah); + } + if (durationMs != 0) { + serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs); + } + if (mPowerModels != null) { + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL, + mPowerModels[componentId]); + } + serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT); + } + + final int customComponentEnd = + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + mCustomPowerComponentCount; + for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; + componentId < customComponentEnd; + componentId++) { + final double powerMah = getConsumedPowerForCustomComponent(componentId); + final long durationMs = getUsageDurationForCustomComponentMillis(componentId); + if (powerMah == 0 && durationMs == 0) { + continue; + } + + serializer.startTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT); + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId); + if (powerMah != 0) { + serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah); + } + if (durationMs != 0) { + serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs); + } + serializer.endTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT); + } + + serializer.endTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS); + } + + + static void parseXml(TypedXmlPullParser parser, PowerComponents.Builder builder) + throws XmlPullParserException, IOException { + int eventType = parser.getEventType(); + if (eventType != XmlPullParser.START_TAG || !parser.getName().equals( + BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) { + throw new XmlPullParserException("Invalid XML parser state"); + } + + while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals( + BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) + && eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + switch (parser.getName()) { + case BatteryUsageStats.XML_TAG_COMPONENT: { + int componentId = -1; + double powerMah = 0; + long durationMs = 0; + int model = BatteryConsumer.POWER_MODEL_UNDEFINED; + for (int i = 0; i < parser.getAttributeCount(); i++) { + switch (parser.getAttributeName(i)) { + case BatteryUsageStats.XML_ATTR_ID: + componentId = parser.getAttributeInt(i); + break; + case BatteryUsageStats.XML_ATTR_POWER: + powerMah = parser.getAttributeDouble(i); + break; + case BatteryUsageStats.XML_ATTR_DURATION: + durationMs = parser.getAttributeLong(i); + break; + case BatteryUsageStats.XML_ATTR_MODEL: + model = parser.getAttributeInt(i); + break; + } + } + builder.setConsumedPower(componentId, powerMah, model); + builder.setUsageDurationMillis(componentId, durationMs); + break; + } + case BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT: { + int componentId = -1; + double powerMah = 0; + long durationMs = 0; + for (int i = 0; i < parser.getAttributeCount(); i++) { + switch (parser.getAttributeName(i)) { + case BatteryUsageStats.XML_ATTR_ID: + componentId = parser.getAttributeInt(i); + break; + case BatteryUsageStats.XML_ATTR_POWER: + powerMah = parser.getAttributeDouble(i); + break; + case BatteryUsageStats.XML_ATTR_DURATION: + durationMs = parser.getAttributeLong(i); + break; + } + } + builder.setConsumedPowerForCustomComponent(componentId, powerMah); + builder.setUsageDurationForCustomComponentMillis(componentId, durationMs); + break; + } + } + } + eventType = parser.next(); + } + } + /** * Builder for PowerComponents. */ static final class Builder { + private static final byte POWER_MODEL_UNINITIALIZED = -1; + private final double[] mPowerComponentsMah; private final String[] mCustomPowerComponentNames; private final long[] mUsageDurationsMs; @@ -311,6 +443,7 @@ class PowerComponents { mUsageDurationsMs = new long[powerComponentCount]; if (includePowerModels) { mPowerModels = new byte[BatteryConsumer.POWER_COMPONENT_COUNT]; + Arrays.fill(mPowerModels, POWER_MODEL_UNINITIALIZED); } else { mPowerModels = null; } @@ -412,12 +545,39 @@ class PowerComponents { return this; } - public void addPowerAndDuration(Builder other) { + public void addPowerAndDuration(PowerComponents.Builder other) { + addPowerAndDuration(other.mPowerComponentsMah, other.mUsageDurationsMs, + other.mPowerModels); + } + + public void addPowerAndDuration(PowerComponents other) { + addPowerAndDuration(other.mPowerComponentsMah, other.mUsageDurationsMs, + other.mPowerModels); + } + + private void addPowerAndDuration(double[] powerComponentsMah, + long[] usageDurationsMs, byte[] powerModels) { + if (mPowerComponentsMah.length != powerComponentsMah.length) { + throw new IllegalArgumentException( + "Number of power components does not match: " + powerComponentsMah.length + + ", expected: " + mPowerComponentsMah.length); + } + for (int i = mPowerComponentsMah.length - 1; i >= 0; i--) { - mPowerComponentsMah[i] += other.mPowerComponentsMah[i]; + mPowerComponentsMah[i] += powerComponentsMah[i]; } for (int i = mUsageDurationsMs.length - 1; i >= 0; i--) { - mUsageDurationsMs[i] += other.mUsageDurationsMs[i]; + mUsageDurationsMs[i] += usageDurationsMs[i]; + } + if (mPowerModels != null && powerModels != null) { + for (int i = mPowerModels.length - 1; i >= 0; i--) { + if (mPowerModels[i] == POWER_MODEL_UNINITIALIZED) { + mPowerModels[i] = powerModels[i]; + } else if (mPowerModels[i] != powerModels[i] + && powerModels[i] != POWER_MODEL_UNINITIALIZED) { + mPowerModels[i] = BatteryConsumer.POWER_MODEL_UNDEFINED; + } + } } } @@ -433,6 +593,19 @@ class PowerComponents { return totalPowerMah; } + private byte[] getPowerModels() { + if (mPowerModels == null) { + return null; + } + + byte[] powerModels = new byte[mPowerModels.length]; + for (int i = mPowerModels.length - 1; i >= 0; i--) { + powerModels[i] = mPowerModels[i] != POWER_MODEL_UNINITIALIZED ? mPowerModels[i] + : BatteryConsumer.POWER_MODEL_UNDEFINED; + } + return powerModels; + } + /** * Creates a read-only object out of the Builder values. */ diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java index 16a6c767da38..bfc4f73835d9 100644 --- a/core/java/android/os/UidBatteryConsumer.java +++ b/core/java/android/os/UidBatteryConsumer.java @@ -19,9 +19,16 @@ package android.os; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.text.TextUtils; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; import com.android.internal.os.PowerCalculator; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -143,13 +150,65 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela return 0; } + /** Serializes this object to XML */ + void writeToXml(TypedXmlSerializer serializer) throws IOException { + if (getConsumedPower() == 0) { + return; + } + + serializer.startTag(null, BatteryUsageStats.XML_TAG_UID); + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_UID, getUid()); + if (!TextUtils.isEmpty(mPackageWithHighestDrain)) { + serializer.attribute(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE, + mPackageWithHighestDrain); + } + serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND, + mTimeInForegroundMs); + serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND, + mTimeInBackgroundMs); + mPowerComponents.writeToXml(serializer); + serializer.endTag(null, BatteryUsageStats.XML_TAG_UID); + } + + /** Parses an XML representation and populates the BatteryUsageStats builder */ + static void createFromXml(TypedXmlPullParser parser, BatteryUsageStats.Builder builder) + throws XmlPullParserException, IOException { + final int uid = parser.getAttributeInt(null, BatteryUsageStats.XML_ATTR_UID); + final UidBatteryConsumer.Builder consumerBuilder = + builder.getOrCreateUidBatteryConsumerBuilder(uid); + + int eventType = parser.getEventType(); + if (eventType != XmlPullParser.START_TAG + || !parser.getName().equals(BatteryUsageStats.XML_TAG_UID)) { + throw new XmlPullParserException("Invalid XML parser state"); + } + + consumerBuilder.setPackageWithHighestDrain( + parser.getAttributeValue(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE)); + consumerBuilder.setTimeInStateMs(STATE_FOREGROUND, + parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND)); + consumerBuilder.setTimeInStateMs(STATE_BACKGROUND, + parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND)); + while (!(eventType == XmlPullParser.END_TAG + && parser.getName().equals(BatteryUsageStats.XML_TAG_UID)) + && eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals(BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) { + PowerComponents.parseXml(parser, consumerBuilder.mPowerComponentsBuilder); + } + } + eventType = parser.next(); + } + } + /** * Builder for UidBatteryConsumer. */ public static final class Builder extends BaseBuilder<Builder> { + private static final String PACKAGE_NAME_UNINITIALIZED = ""; private final BatteryStats.Uid mBatteryStatsUid; private final int mUid; - private String mPackageWithHighestDrain; + private String mPackageWithHighestDrain = PACKAGE_NAME_UNINITIALIZED; public long mTimeInForegroundMs; public long mTimeInBackgroundMs; private boolean mExcludeFromBatteryUsageStats; @@ -161,8 +220,19 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela mUid = batteryStatsUid.getUid(); } + public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels, + int uid) { + super(customPowerComponentNames, includePowerModels); + mBatteryStatsUid = null; + mUid = uid; + } + @NonNull public BatteryStats.Uid getBatteryStatsUid() { + if (mBatteryStatsUid == null) { + throw new IllegalStateException( + "UidBatteryConsumer.Builder was initialized without a BatteryStats.Uid"); + } return mBatteryStatsUid; } @@ -176,7 +246,7 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela */ @NonNull public Builder setPackageWithHighestDrain(@Nullable String packageName) { - mPackageWithHighestDrain = packageName; + mPackageWithHighestDrain = TextUtils.nullIfEmpty(packageName); return this; } @@ -208,6 +278,30 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela } /** + * Adds power and usage duration from the supplied UidBatteryConsumer. + */ + public Builder add(UidBatteryConsumer consumer) { + mPowerComponentsBuilder.addPowerAndDuration(consumer.mPowerComponents); + mTimeInBackgroundMs += consumer.mTimeInBackgroundMs; + mTimeInForegroundMs += consumer.mTimeInForegroundMs; + + if (mPackageWithHighestDrain == PACKAGE_NAME_UNINITIALIZED) { + mPackageWithHighestDrain = consumer.mPackageWithHighestDrain; + } else if (!TextUtils.equals(mPackageWithHighestDrain, + consumer.mPackageWithHighestDrain)) { + // Consider combining two UidBatteryConsumers with this distribution + // of power drain between packages: + // (package1=100, package2=10) and (package1=100, package2=101). + // Since we don't know the actual power distribution between packages at this + // point, we have no way to correctly declare package1 as the winner. + // The naive logic of picking the consumer with the higher total consumed + // power would produce an incorrect result. + mPackageWithHighestDrain = null; + } + return this; + } + + /** * Returns true if this UidBatteryConsumer must be excluded from the * BatteryUsageStats. */ @@ -220,6 +314,9 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela */ @NonNull public UidBatteryConsumer build() { + if (mPackageWithHighestDrain == PACKAGE_NAME_UNINITIALIZED) { + mPackageWithHighestDrain = null; + } return new UidBatteryConsumer(this); } } diff --git a/core/java/android/os/UserBatteryConsumer.java b/core/java/android/os/UserBatteryConsumer.java index 429d2c53a836..b508a8cd98ae 100644 --- a/core/java/android/os/UserBatteryConsumer.java +++ b/core/java/android/os/UserBatteryConsumer.java @@ -17,9 +17,15 @@ package android.os; import android.annotation.NonNull; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; import com.android.internal.os.PowerCalculator; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -85,6 +91,42 @@ public class UserBatteryConsumer extends BatteryConsumer implements Parcelable { return 0; } + /** Serializes this object to XML */ + void writeToXml(TypedXmlSerializer serializer) throws IOException { + if (getConsumedPower() == 0) { + return; + } + + serializer.startTag(null, BatteryUsageStats.XML_TAG_USER); + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_USER_ID, getUserId()); + mPowerComponents.writeToXml(serializer); + serializer.endTag(null, BatteryUsageStats.XML_TAG_USER); + } + + /** Parses an XML representation and populates the BatteryUsageStats builder */ + static void createFromXml(TypedXmlPullParser parser, BatteryUsageStats.Builder builder) + throws XmlPullParserException, IOException { + final int userId = parser.getAttributeInt(null, BatteryUsageStats.XML_ATTR_USER_ID); + final UserBatteryConsumer.Builder consumerBuilder = + builder.getOrCreateUserBatteryConsumerBuilder(userId); + + int eventType = parser.getEventType(); + if (eventType != XmlPullParser.START_TAG + || !parser.getName().equals(BatteryUsageStats.XML_TAG_USER)) { + throw new XmlPullParserException("Invalid XML parser state"); + } + while (!(eventType == XmlPullParser.END_TAG + && parser.getName().equals(BatteryUsageStats.XML_TAG_USER)) + && eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals(BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) { + PowerComponents.parseXml(parser, consumerBuilder.mPowerComponentsBuilder); + } + } + eventType = parser.next(); + } + } + /** * Builder for UserBatteryConsumer. */ diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index c22224dd0da2..7edd6e6597b2 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1322,6 +1322,24 @@ public class UserManager { "disallow_camera_toggle"; /** + * This is really not a user restriction in the normal sense. This can't be set to a user, + * via UserManager nor via DevicePolicyManager. This is not even set in UserSettingsUtils. + * This is defined here purely for convenience within the settings app. + * + * TODO(b/191306258): Refactor the Settings app to remove the need for this field, and delete it + * + * Specifies whether biometrics are available to the user. This is used internally only, + * as a means of communications between biometric settings and + * {@link com.android.settingslib.enterprise.ActionDisabledByAdminControllerFactory}. + * + * @see {@link android.hardware.biometrics.ParentalControlsUtilsInternal} + * @see {@link com.android.settings.biometrics.ParentalControlsUtils} + * + * @hide + */ + public static final String DISALLOW_BIOMETRIC = "disallow_biometric"; + + /** * Application restriction key that is used to indicate the pending arrival * of real restrictions for the app. * @@ -1415,6 +1433,7 @@ public class UserManager { DISALLOW_MICROPHONE_TOGGLE, DISALLOW_CAMERA_TOGGLE, KEY_RESTRICTIONS_PENDING, + DISALLOW_BIOMETRIC, }) @Retention(RetentionPolicy.SOURCE) public @interface UserRestrictionKey {} diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 12d905588e1e..ff692818863a 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -50,6 +50,7 @@ import android.util.TypedXmlSerializer; import android.util.proto.ProtoOutputStream; import com.android.internal.R; +import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -162,6 +163,7 @@ public class ZenModeConfig implements Parcelable { private static final String RULE_ATT_ENABLED = "enabled"; private static final String RULE_ATT_SNOOZING = "snoozing"; private static final String RULE_ATT_NAME = "name"; + private static final String RULE_ATT_PKG = "pkg"; private static final String RULE_ATT_COMPONENT = "component"; private static final String RULE_ATT_CONFIG_ACTIVITY = "configActivity"; private static final String RULE_ATT_ZEN = "zen"; @@ -671,11 +673,11 @@ public class ZenModeConfig implements Parcelable { rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID); rt.component = safeComponentName(parser, RULE_ATT_COMPONENT); rt.configurationActivity = safeComponentName(parser, RULE_ATT_CONFIG_ACTIVITY); - rt.pkg = (rt.component != null) - ? rt.component.getPackageName() - : (rt.configurationActivity != null) - ? rt.configurationActivity.getPackageName() - : null; + rt.pkg = XmlUtils.readStringAttribute(parser, RULE_ATT_PKG); + if (rt.pkg == null) { + // backfill from component, if present. configActivity is not safe to backfill from + rt.pkg = rt.component != null ? rt.component.getPackageName() : null; + } rt.creationTime = safeLong(parser, RULE_ATT_CREATION_TIME, 0); rt.enabler = parser.getAttributeValue(null, RULE_ATT_ENABLER); rt.condition = readConditionXml(parser); @@ -697,6 +699,9 @@ public class ZenModeConfig implements Parcelable { out.attribute(null, RULE_ATT_NAME, rule.name); } out.attributeInt(null, RULE_ATT_ZEN, rule.zenMode); + if (rule.pkg != null) { + out.attribute(null, RULE_ATT_PKG, rule.pkg); + } if (rule.component != null) { out.attribute(null, RULE_ATT_COMPONENT, rule.component.flattenToString()); } diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl index 3eb35c2c5e8a..8b8dba89ea67 100644 --- a/core/java/android/window/ITaskOrganizer.aidl +++ b/core/java/android/window/ITaskOrganizer.aidl @@ -52,6 +52,11 @@ oneway interface ITaskOrganizer { void copySplashScreenView(int taskId); /** + * Called when the Task removed the splash screen. + */ + void onAppSplashScreenViewRemoved(int taskId); + + /** * A callback when the Task is available for the registered organizer. The client is responsible * for releasing the SurfaceControl in the callback. For non-root tasks, the leash may initially * be hidden so it is up to the organizer to show this task. diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index 6772afeb0270..4a3bf91645f2 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -34,8 +34,10 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import android.os.RemoteCallback; import android.os.Trace; import android.util.AttributeSet; import android.util.Log; @@ -76,7 +78,7 @@ import java.time.Instant; */ public final class SplashScreenView extends FrameLayout { private static final String TAG = SplashScreenView.class.getSimpleName(); - private static final boolean DEBUG = false; + private static final boolean DEBUG = Build.IS_DEBUGGABLE; private static final int LIGHT_BARS_MASK = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS @@ -85,6 +87,7 @@ public final class SplashScreenView extends FrameLayout { | FLAG_TRANSLUCENT_NAVIGATION | FLAG_TRANSLUCENT_STATUS; private boolean mNotCopyable; + private boolean mIsCopied; private int mInitBackgroundColor; private int mInitIconBackgroundColor; private View mIconView; @@ -103,6 +106,10 @@ public final class SplashScreenView extends FrameLayout { private SurfaceControlViewHost.SurfacePackage mSurfacePackage; @Nullable private SurfaceView mSurfaceView; + @Nullable + private SurfaceControlViewHost mSurfaceHost; + @Nullable + private RemoteCallback mClientCallback; // cache original window and status private Window mWindow; @@ -127,6 +134,7 @@ public final class SplashScreenView extends FrameLayout { private Bitmap mParceledIconBitmap; private Drawable mIconDrawable; private SurfaceControlViewHost.SurfacePackage mSurfacePackage; + private RemoteCallback mClientCallback; private int mBrandingImageWidth; private int mBrandingImageHeight; private Drawable mBrandingDrawable; @@ -161,6 +169,7 @@ public final class SplashScreenView extends FrameLayout { } mIconAnimationStart = Instant.ofEpochMilli(parcelable.mIconAnimationStartMillis); mIconAnimationDuration = Duration.ofMillis(parcelable.mIconAnimationDurationMillis); + mClientCallback = parcelable.mClientCallback; if (DEBUG) { Log.d(TAG, String.format("Building from parcel drawable: %s", mIconDrawable)); } @@ -228,6 +237,7 @@ public final class SplashScreenView extends FrameLayout { view.mInitBackgroundColor = mBackgroundColor; view.mInitIconBackgroundColor = mIconBackground; view.setBackgroundColor(mBackgroundColor); + view.mClientCallback = mClientCallback; view.mBrandingImageView = view.findViewById(R.id.splashscreen_branding_view); @@ -285,7 +295,8 @@ public final class SplashScreenView extends FrameLayout { if (mSurfacePackage == null) { if (DEBUG) { Log.d(TAG, - "Creating Original SurfacePackage. SurfaceView: " + surfaceView); + "SurfaceControlViewHost created on thread " + + Thread.currentThread().getId()); } SurfaceControlViewHost viewHost = new SurfaceControlViewHost(mContext, @@ -297,6 +308,7 @@ public final class SplashScreenView extends FrameLayout { SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage(); surfaceView.setChildSurfacePackage(surfacePackage); view.mSurfacePackage = surfacePackage; + view.mSurfaceHost = viewHost; view.mSurfacePackageCopy = new SurfaceControlViewHost.SurfacePackage( surfacePackage); } else { @@ -357,6 +369,7 @@ public final class SplashScreenView extends FrameLayout { * @hide */ public void onCopied() { + mIsCopied = true; if (mSurfaceView == null) { return; } @@ -369,6 +382,12 @@ public final class SplashScreenView extends FrameLayout { mSurfacePackage = null; } + /** @hide **/ + @Nullable + public SurfaceControlViewHost getSurfaceHost() { + return mSurfaceHost; + } + @Override public void setAlpha(float alpha) { super.setAlpha(alpha); @@ -407,8 +426,7 @@ public final class SplashScreenView extends FrameLayout { if (DEBUG) { mSurfacePackage.getSurfaceControl().addOnReparentListener( (transaction, parent) -> Log.e(TAG, - String.format("SurfacePackage'surface reparented.\n Parent: %s", - parent), new Throwable())); + String.format("SurfacePackage'surface reparented to %s", parent))); Log.d(TAG, "Transferring surface " + mSurfaceView.toString()); } mSurfaceView.setChildSurfacePackage(mSurfacePackage); @@ -466,9 +484,36 @@ public final class SplashScreenView extends FrameLayout { mHasRemoved = true; } + /** @hide **/ + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + releaseAnimationSurfaceHost(); + } + + private void releaseAnimationSurfaceHost() { + if (mSurfaceHost != null && !mIsCopied) { + final SurfaceControlViewHost finalSurfaceHost = mSurfaceHost; + mSurfaceHost = null; + finalSurfaceHost.getView().post(() -> { + if (DEBUG) { + Log.d(TAG, + "Shell removed splash screen." + + " Releasing SurfaceControlViewHost on thread #" + + Thread.currentThread().getId()); + } + finalSurfaceHost.release(); + }); + } else if (mSurfacePackage != null && mSurfaceHost == null) { + mSurfacePackage = null; + mClientCallback.sendResult(null); + } + } + /** * Called when this view is attached to an activity. This also makes SystemUI colors * transparent so the content of splash screen view can draw fully. + * * @hide */ public void attachHostActivityAndSetSystemUIColors(Activity activity, Window window) { @@ -585,6 +630,7 @@ public final class SplashScreenView extends FrameLayout { private long mIconAnimationDurationMillis; private SurfaceControlViewHost.SurfacePackage mSurfacePackage; + private RemoteCallback mClientCallback; public SplashScreenViewParcelable(SplashScreenView view) { mIconSize = view.mIconView.getWidth(); @@ -641,6 +687,7 @@ public final class SplashScreenView extends FrameLayout { mIconAnimationDurationMillis = source.readLong(); mIconBackground = source.readInt(); mSurfacePackage = source.readTypedObject(SurfaceControlViewHost.SurfacePackage.CREATOR); + mClientCallback = source.readTypedObject(RemoteCallback.CREATOR); } @Override @@ -660,6 +707,7 @@ public final class SplashScreenView extends FrameLayout { dest.writeLong(mIconAnimationDurationMillis); dest.writeInt(mIconBackground); dest.writeTypedObject(mSurfacePackage, flags); + dest.writeTypedObject(mClientCallback, flags); } public static final @NonNull Parcelable.Creator<SplashScreenViewParcelable> CREATOR = @@ -697,5 +745,13 @@ public final class SplashScreenView extends FrameLayout { int getIconBackground() { return mIconBackground; } + + /** + * Sets the {@link RemoteCallback} that will be called by the client to notify the shell + * of the removal of the {@link SplashScreenView}. + */ + public void setClientCallback(@NonNull RemoteCallback clientCallback) { + mClientCallback = clientCallback; + } } } diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index 3340cf4fb707..73995491668a 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -117,6 +117,16 @@ public class TaskOrganizer extends WindowOrganizer { public void copySplashScreenView(int taskId) {} /** + * Notify the shell ({@link com.android.wm.shell.ShellTaskOrganizer} that the client has + * removed the splash screen view. + * @see com.android.wm.shell.ShellTaskOrganizer#onAppSplashScreenViewRemoved(int) + * @see SplashScreenView#remove() + */ + @BinderThread + public void onAppSplashScreenViewRemoved(int taskId) { + } + + /** * Called when a task with the registered windowing mode can be controlled by this task * organizer. For non-root tasks, the leash may initially be hidden so it is up to the organizer * to show this task. @@ -236,11 +246,16 @@ public class TaskOrganizer extends WindowOrganizer { } @Override - public void copySplashScreenView(int taskId) { + public void copySplashScreenView(int taskId) { mExecutor.execute(() -> TaskOrganizer.this.copySplashScreenView(taskId)); } @Override + public void onAppSplashScreenViewRemoved(int taskId) { + mExecutor.execute(() -> TaskOrganizer.this.onAppSplashScreenViewRemoved(taskId)); + } + + @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { mExecutor.execute(() -> TaskOrganizer.this.onTaskAppeared(taskInfo, leash)); } diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java index bd908900fccc..19183b8eb9e7 100644 --- a/core/java/com/android/internal/display/BrightnessSynchronizer.java +++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java @@ -31,9 +31,6 @@ import android.provider.Settings; import android.util.MathUtils; import android.view.Display; -import java.util.LinkedList; -import java.util.Queue; - /** * BrightnessSynchronizer helps convert between the int (old) system and float * (new) system for storing the brightness. It has methods to convert between the two and also @@ -43,12 +40,11 @@ public class BrightnessSynchronizer { private static final int MSG_UPDATE_FLOAT = 1; private static final int MSG_UPDATE_INT = 2; + private static final int MSG_UPDATE_BOTH = 3; private static final String TAG = "BrightnessSynchronizer"; private static final Uri BRIGHTNESS_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS); - private static final Uri BRIGHTNESS_FLOAT_URI = - Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT); // The tolerance within which we consider brightness values approximately equal to eachother. // This value is approximately 1/3 of the smallest possible brightness value. @@ -57,8 +53,6 @@ public class BrightnessSynchronizer { private DisplayManager mDisplayManager; private final Context mContext; - private final Queue<Object> mWriteHistory = new LinkedList<>(); - private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { @@ -69,6 +63,9 @@ public class BrightnessSynchronizer { case MSG_UPDATE_INT: updateBrightnessIntFromFloat(Float.intBitsToFloat(msg.arg1)); break; + case MSG_UPDATE_BOTH: + updateBoth(Float.intBitsToFloat(msg.arg1)); + break; default: super.handleMessage(msg); } @@ -139,7 +136,7 @@ public class BrightnessSynchronizer { /** * Translates specified value from the float brightness system to the int brightness system, - * given the min/max of each range. Accounts for special values such as OFF and invalid values. + * given the min/max of each range. Accounts for special values such as OFF and invalid values. * Value returned as a float primitive (to preserve precision), but is a value within the * int-system range. */ @@ -168,49 +165,63 @@ public class BrightnessSynchronizer { } /** - * Updates the float setting based on a passed in int value. This is called whenever the int - * setting changes. mWriteHistory keeps a record of the values that been written to the settings - * from either this method or updateBrightnessIntFromFloat. This is to ensure that the value - * being set is due to an external value being set, rather than the updateBrightness* methods. - * The intention of this is to avoid race conditions when the setting is being changed - * frequently and to ensure we are not reacting to settings changes from this file. + * Updates the settings based on a passed in int value. This is called whenever the int + * setting changes. mPreferredSettingValue holds the most recently updated brightness value + * as a float that we would like the display to be set to. + * + * We then schedule an update to both the int and float settings, but, remove all the other + * messages to update all, to prevent us getting stuck in a loop. + * * @param value Brightness value as int to store in the float setting. */ private void updateBrightnessFloatFromInt(int value) { - Object topOfQueue = mWriteHistory.peek(); - if (topOfQueue != null && topOfQueue.equals(value)) { - mWriteHistory.poll(); - } else { - if (brightnessFloatToInt(mPreferredSettingValue) == value) { - return; - } - float newBrightnessFloat = brightnessIntToFloat(value); - mWriteHistory.offer(newBrightnessFloat); - mPreferredSettingValue = newBrightnessFloat; - mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, newBrightnessFloat); + if (brightnessFloatToInt(mPreferredSettingValue) == value) { + return; } + + mPreferredSettingValue = brightnessIntToFloat(value); + final int newBrightnessAsIntBits = Float.floatToIntBits(mPreferredSettingValue); + mHandler.removeMessages(MSG_UPDATE_BOTH); + mHandler.obtainMessage(MSG_UPDATE_BOTH, newBrightnessAsIntBits, 0).sendToTarget(); } /** - * Updates the int setting based on a passed in float value. This is called whenever the float - * setting changes. mWriteHistory keeps a record of the values that been written to the settings - * from either this method or updateBrightnessFloatFromInt. This is to ensure that the value - * being set is due to an external value being set, rather than the updateBrightness* methods. - * The intention of this is to avoid race conditions when the setting is being changed - * frequently and to ensure we are not reacting to settings changes from this file. + * Updates the settings based on a passed in float value. This is called whenever the float + * setting changes. mPreferredSettingValue holds the most recently updated brightness value + * as a float that we would like the display to be set to. + * + * We then schedule an update to both the int and float settings, but, remove all the other + * messages to update all, to prevent us getting stuck in a loop. + * * @param value Brightness setting as float to store in int setting. */ private void updateBrightnessIntFromFloat(float value) { - int newBrightnessInt = brightnessFloatToInt(value); - Object topOfQueue = mWriteHistory.peek(); - if (topOfQueue != null && topOfQueue.equals(value)) { - mWriteHistory.poll(); - } else { - mWriteHistory.offer(newBrightnessInt); - mPreferredSettingValue = value; + if (floatEquals(mPreferredSettingValue, value)) { + return; + } + + mPreferredSettingValue = value; + final int newBrightnessAsIntBits = Float.floatToIntBits(mPreferredSettingValue); + mHandler.removeMessages(MSG_UPDATE_BOTH); + mHandler.obtainMessage(MSG_UPDATE_BOTH, newBrightnessAsIntBits, 0).sendToTarget(); + } + + + /** + * Updates both setting values if they have changed + * mDisplayManager.setBrightness automatically checks for changes + * Settings.System.putIntForUser needs to be checked, to prevent an extra callback to this class + * + * @param newBrightnessFloat Brightness setting as float to store in both settings + */ + private void updateBoth(float newBrightnessFloat) { + int newBrightnessInt = brightnessFloatToInt(newBrightnessFloat); + mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, newBrightnessFloat); + if (getScreenBrightnessInt(mContext) != newBrightnessInt) { Settings.System.putIntForUser(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, newBrightnessInt, UserHandle.USER_CURRENT); } + } /** diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 26d6a0c74b08..aabcd7f82ac7 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -43,6 +43,10 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR; @@ -53,6 +57,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; import android.annotation.IntDef; import android.annotation.NonNull; @@ -153,6 +158,11 @@ public class InteractionJankMonitor { public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET = 27; public static final int CUJ_SETTINGS_PAGE_SCROLL = 28; public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = 29; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = 30; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = 31; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33; + public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34; private static final int NO_STATSD_LOGGING = -1; @@ -191,6 +201,11 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, }; private static volatile InteractionJankMonitor sInstance; @@ -240,6 +255,11 @@ public class InteractionJankMonitor { CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET, CUJ_SETTINGS_PAGE_SCROLL, CUJ_LOCKSCREEN_UNLOCK_ANIMATION, + CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON, + CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER, + CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE, + CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON, + CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -578,6 +598,16 @@ public class InteractionJankMonitor { return "SETTINGS_PAGE_SCROLL"; case CUJ_LOCKSCREEN_UNLOCK_ANIMATION: return "LOCKSCREEN_UNLOCK_ANIMATION"; + case CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON: + return "SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON"; + case CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER: + return "SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER"; + case CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE: + return "SHADE_APP_LAUNCH_FROM_QS_TILE"; + case CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON: + return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON"; + case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP: + return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 5dfc5faead43..945a6ab11856 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -341,6 +341,19 @@ public class BatteryStatsImpl extends BatteryStats { } } + /** + * Listener for the battery stats reset. + */ + public interface BatteryResetListener { + + /** + * Callback invoked immediately prior to resetting battery stats. + */ + void prepareForBatteryStatsReset(); + } + + private BatteryResetListener mBatteryResetListener; + public interface BatteryCallback { public void batteryNeedsCpuUpdate(); public void batteryPowerChanged(boolean onBattery); @@ -10736,6 +10749,10 @@ public class BatteryStatsImpl extends BatteryStats { } } + PowerProfile getPowerProfile() { + return mPowerProfile; + } + /** * Starts tracking CPU time-in-state for threads of the system server process, * keeping a separate account of threads receiving incoming binder calls. @@ -11184,6 +11201,10 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter.reset(false, elapsedRealtimeUs); } + public void setBatteryResetListener(BatteryResetListener batteryResetListener) { + mBatteryResetListener = batteryResetListener; + } + public void resetAllStatsCmdLocked() { final long mSecUptime = mClocks.uptimeMillis(); long uptimeUs = mSecUptime * 1000; @@ -11219,6 +11240,10 @@ public class BatteryStatsImpl extends BatteryStats { } private void resetAllStatsLocked(long uptimeMillis, long elapsedRealtimeMillis) { + if (mBatteryResetListener != null) { + mBatteryResetListener.prepareForBatteryStatsReset(); + } + final long uptimeUs = uptimeMillis * 1000; final long elapsedRealtimeUs = elapsedRealtimeMillis * 1000; mStartCount = 0; diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java index 3aaccdd71844..8943db6c8a1e 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java @@ -38,14 +38,24 @@ import java.util.Map; public class BatteryUsageStatsProvider { private final Context mContext; private final BatteryStats mStats; + private final BatteryUsageStatsStore mBatteryUsageStatsStore; private final PowerProfile mPowerProfile; private final Object mLock = new Object(); private List<PowerCalculator> mPowerCalculators; public BatteryUsageStatsProvider(Context context, BatteryStats stats) { + this(context, stats, null); + } + + @VisibleForTesting + public BatteryUsageStatsProvider(Context context, BatteryStats stats, + BatteryUsageStatsStore batteryUsageStatsStore) { mContext = context; mStats = stats; - mPowerProfile = new PowerProfile(mContext); + mBatteryUsageStatsStore = batteryUsageStatsStore; + mPowerProfile = stats instanceof BatteryStatsImpl + ? ((BatteryStatsImpl) stats).getPowerProfile() + : new PowerProfile(context); } private List<PowerCalculator> getPowerCalculators() { @@ -126,6 +136,15 @@ public class BatteryUsageStatsProvider { private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query, long currentTimeMs) { + if (query.getToTimestamp() == 0) { + return getCurrentBatteryUsageStats(query, currentTimeMs); + } else { + return getAggregatedBatteryUsageStats(query); + } + } + + private BatteryUsageStats getCurrentBatteryUsageStats(BatteryUsageStatsQuery query, + long currentTimeMs) { final long realtimeUs = elapsedRealtime() * 1000; final long uptimeUs = uptimeMillis() * 1000; @@ -209,6 +228,25 @@ public class BatteryUsageStatsProvider { BatteryStats.STATS_SINCE_CHARGED) / 1000; } + private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryUsageStatsQuery query) { + final boolean includePowerModels = (query.getFlags() + & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0; + + final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( + mStats.getCustomEnergyConsumerNames(), includePowerModels); + final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps(); + for (long timestamp : timestamps) { + if (timestamp > query.getFromTimestamp() && timestamp <= query.getToTimestamp()) { + final BatteryUsageStats snapshot = + mBatteryUsageStatsStore.loadBatteryUsageStats(timestamp); + if (snapshot != null) { + builder.add(snapshot); + } + } + } + return builder.build(); + } + private long elapsedRealtime() { if (mStats instanceof BatteryStatsImpl) { return ((BatteryStatsImpl) mStats).mClocks.elapsedRealtime(); diff --git a/core/java/com/android/internal/os/BatteryUsageStatsStore.java b/core/java/com/android/internal/os/BatteryUsageStatsStore.java new file mode 100644 index 000000000000..5c976025d39d --- /dev/null +++ b/core/java/com/android/internal/os/BatteryUsageStatsStore.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2021 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.internal.os; + +import android.annotation.Nullable; +import android.content.Context; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.os.Handler; +import android.util.AtomicFile; +import android.util.LongArray; +import android.util.Slog; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import android.util.Xml; + +import com.android.internal.annotations.VisibleForTesting; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.charset.StandardCharsets; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.TreeMap; + +/** + * A storage mechanism for BatteryUsageStats snapshots. + */ +public class BatteryUsageStatsStore { + private static final String TAG = "BatteryUsageStatsStore"; + + private static final List<BatteryUsageStatsQuery> BATTERY_USAGE_STATS_QUERY = List.of( + new BatteryUsageStatsQuery.Builder() + .setMaxStatsAgeMs(0) + .includePowerModels() + .build()); + private static final String BATTERY_USAGE_STATS_DIR = "battery-usage-stats"; + private static final String SNAPSHOT_FILE_EXTENSION = ".bus"; + private static final String DIR_LOCK_FILENAME = ".lock"; + private static final String CONFIG_FILENAME = "config"; + private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY = + "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP"; + private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 100 * 1024; + + private final Context mContext; + private final BatteryStatsImpl mBatteryStats; + private final File mStoreDir; + private final File mLockFile; + private final AtomicFile mConfigFile; + private final long mMaxStorageBytes; + private final Handler mHandler; + private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; + + public BatteryUsageStatsStore(Context context, BatteryStatsImpl stats, File systemDir, + Handler handler) { + this(context, stats, systemDir, handler, MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES); + } + + @VisibleForTesting + public BatteryUsageStatsStore(Context context, BatteryStatsImpl batteryStats, File systemDir, + Handler handler, long maxStorageBytes) { + mContext = context; + mBatteryStats = batteryStats; + mStoreDir = new File(systemDir, BATTERY_USAGE_STATS_DIR); + mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME); + mConfigFile = new AtomicFile(new File(mStoreDir, CONFIG_FILENAME)); + mHandler = handler; + mMaxStorageBytes = maxStorageBytes; + mBatteryStats.setBatteryResetListener(this::prepareForBatteryStatsReset); + mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(mContext, mBatteryStats); + } + + private void prepareForBatteryStatsReset() { + final List<BatteryUsageStats> stats = + mBatteryUsageStatsProvider.getBatteryUsageStats(BATTERY_USAGE_STATS_QUERY); + if (stats.isEmpty()) { + Slog.wtf(TAG, "No battery usage stats generated"); + return; + } + + mHandler.post(() -> storeBatteryUsageStats(stats.get(0))); + } + + private void storeBatteryUsageStats(BatteryUsageStats stats) { + try (FileLock lock = lockSnapshotDirectory()) { + if (!mStoreDir.exists()) { + if (!mStoreDir.mkdirs()) { + Slog.e(TAG, + "Could not create a directory for battery usage stats snapshots"); + return; + } + } + File file = makeSnapshotFilename(stats.getStatsEndTimestamp()); + try { + writeXmlFileLocked(stats, file); + } catch (Exception e) { + Slog.e(TAG, "Cannot save battery usage stats", e); + } + + removeOldSnapshotsLocked(); + } catch (IOException e) { + Slog.e(TAG, "Cannot lock battery usage stats directory", e); + } + } + + /** + * Returns the timestamps of the stored BatteryUsageStats snapshots. The timestamp corresponds + * to the time the snapshot was taken {@link BatteryUsageStats#getStatsEndTimestamp()}. + */ + public long[] listBatteryUsageStatsTimestamps() { + LongArray timestamps = new LongArray(100); + try (FileLock lock = lockSnapshotDirectory()) { + for (File file : mStoreDir.listFiles()) { + String fileName = file.getName(); + if (fileName.endsWith(SNAPSHOT_FILE_EXTENSION)) { + try { + String fileNameWithoutExtension = fileName.substring(0, + fileName.length() - SNAPSHOT_FILE_EXTENSION.length()); + timestamps.add(Long.parseLong(fileNameWithoutExtension)); + } catch (NumberFormatException e) { + Slog.wtf(TAG, "Invalid format of BatteryUsageStats snapshot file name: " + + fileName); + } + } + } + } catch (IOException e) { + Slog.e(TAG, "Cannot lock battery usage stats directory", e); + } + return timestamps.toArray(); + } + + /** + * Reads the specified snapshot of BatteryUsageStats. Returns null if the snapshot + * does not exist. + */ + @Nullable + public BatteryUsageStats loadBatteryUsageStats(long timestamp) { + try (FileLock lock = lockSnapshotDirectory()) { + File file = makeSnapshotFilename(timestamp); + try { + return readXmlFileLocked(file); + } catch (Exception e) { + Slog.e(TAG, "Cannot read battery usage stats", e); + } + } catch (IOException e) { + Slog.e(TAG, "Cannot lock battery usage stats directory", e); + } + return null; + } + + /** + * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull + * in persistent file. + */ + public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) { + Properties props = new Properties(); + try (FileLock lock = lockSnapshotDirectory()) { + try (InputStream in = mConfigFile.openRead()) { + props.load(in); + } catch (IOException e) { + Slog.e(TAG, "Cannot load config file " + mConfigFile, e); + } + props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, + String.valueOf(timestamp)); + FileOutputStream out = null; + try { + out = mConfigFile.startWrite(); + props.store(out, "Statsd atom pull timestamps"); + mConfigFile.finishWrite(out); + } catch (IOException e) { + mConfigFile.failWrite(out); + Slog.e(TAG, "Cannot save config file " + mConfigFile, e); + } + } catch (IOException e) { + Slog.e(TAG, "Cannot lock battery usage stats directory", e); + } + } + + /** + * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET + * statsd atom pull. + */ + public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() { + Properties props = new Properties(); + try (FileLock lock = lockSnapshotDirectory()) { + try (InputStream in = mConfigFile.openRead()) { + props.load(in); + } catch (IOException e) { + Slog.e(TAG, "Cannot load config file " + mConfigFile, e); + } + } catch (IOException e) { + Slog.e(TAG, "Cannot lock battery usage stats directory", e); + } + return Long.parseLong( + props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0")); + } + + private FileLock lockSnapshotDirectory() throws IOException { + mLockFile.getParentFile().mkdirs(); + mLockFile.createNewFile(); + return FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock(); + } + + /** + * Creates a file name by formatting the timestamp as 19-digit zero-padded number. + * This ensures that sorted directory list follows the chronological order. + */ + private File makeSnapshotFilename(long statsEndTimestamp) { + return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", statsEndTimestamp) + + SNAPSHOT_FILE_EXTENSION); + } + + private void writeXmlFileLocked(BatteryUsageStats stats, File file) throws IOException { + try (OutputStream out = new FileOutputStream(file)) { + TypedXmlSerializer serializer = Xml.newBinarySerializer(); + serializer.setOutput(out, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + stats.writeXml(serializer); + serializer.endDocument(); + } + } + + private BatteryUsageStats readXmlFileLocked(File file) + throws IOException, XmlPullParserException { + try (InputStream in = new FileInputStream(file)) { + TypedXmlPullParser parser = Xml.newBinaryPullParser(); + parser.setInput(in, StandardCharsets.UTF_8.name()); + return BatteryUsageStats.createFromXml(parser); + } + } + + private void removeOldSnapshotsLocked() { + // Read the directory list into a _sorted_ map. The alphanumeric ordering + // corresponds to the historical order of snapshots because the file names + // are timestamps zero-padded to the same length. + long totalSize = 0; + TreeMap<File, Long> mFileSizes = new TreeMap<>(); + for (File file : mStoreDir.listFiles()) { + final long fileSize = file.length(); + totalSize += fileSize; + if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) { + mFileSizes.put(file, fileSize); + } + } + + while (totalSize > mMaxStorageBytes) { + final Map.Entry<File, Long> entry = mFileSizes.firstEntry(); + if (entry == null) { + break; + } + + File file = entry.getKey(); + if (!file.delete()) { + Slog.e(TAG, "Cannot delete battery usage stats " + file); + } + totalSize -= entry.getValue(); + mFileSizes.remove(file); + } + } +} diff --git a/core/tests/coretests/src/android/os/PackageTagsListTest.java b/core/tests/coretests/src/android/os/PackageTagsListTest.java index 518e02e44b06..9034a5ccdd7f 100644 --- a/core/tests/coretests/src/android/os/PackageTagsListTest.java +++ b/core/tests/coretests/src/android/os/PackageTagsListTest.java @@ -30,6 +30,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.Arrays; +import java.util.Collections; @Presubmit @RunWith(AndroidJUnit4.class) @@ -40,7 +41,8 @@ public class PackageTagsListTest { PackageTagsList.Builder builder = new PackageTagsList.Builder() .add("package1", "attr1") .add("package1", "attr2") - .add("package2"); + .add("package2") + .add("package4", Arrays.asList("attr1", "attr2")); PackageTagsList list = builder.build(); assertTrue(list.contains(builder.build())); @@ -49,10 +51,13 @@ public class PackageTagsListTest { assertTrue(list.contains("package2", "attr1")); assertTrue(list.contains("package2", "attr2")); assertTrue(list.contains("package2", "attr3")); + assertTrue(list.contains("package4", "attr1")); + assertTrue(list.contains("package4", "attr2")); assertTrue(list.containsAll("package2")); assertTrue(list.includes("package1")); assertTrue(list.includes("package2")); assertFalse(list.contains("package1", "attr3")); + assertFalse(list.contains("package4", "attr3")); assertFalse(list.containsAll("package1")); assertFalse(list.includes("package3")); @@ -92,6 +97,51 @@ public class PackageTagsListTest { } @Test + public void testPackageTagsList_Remove() { + PackageTagsList.Builder builder = new PackageTagsList.Builder() + .add("package1", "attr1") + .add("package1", "attr2") + .add("package2") + .add("package4", Arrays.asList("attr1", "attr2", "attr3")) + .add("package3", "attr1") + .remove("package1", "attr1") + .remove("package1", "attr2") + .remove("package2", "attr1") + .remove("package4", Arrays.asList("attr1", "attr2")) + .remove("package3"); + PackageTagsList list = builder.build(); + + assertTrue(list.contains(builder.build())); + assertFalse(list.contains("package1", "attr1")); + assertFalse(list.contains("package1", "attr2")); + assertTrue(list.contains("package2", "attr1")); + assertTrue(list.contains("package2", "attr2")); + assertTrue(list.contains("package2", "attr3")); + assertFalse(list.contains("package3", "attr1")); + assertFalse(list.contains("package4", "attr1")); + assertFalse(list.contains("package4", "attr2")); + assertTrue(list.contains("package4", "attr3")); + assertTrue(list.containsAll("package2")); + assertFalse(list.includes("package1")); + assertTrue(list.includes("package2")); + assertFalse(list.includes("package3")); + assertTrue(list.includes("package4")); + } + + @Test + public void testPackageTagsList_EmptyCollections() { + PackageTagsList.Builder builder = new PackageTagsList.Builder() + .add("package1", Collections.emptyList()) + .add("package2") + .remove("package2", Collections.emptyList()); + PackageTagsList list = builder.build(); + + assertTrue(list.contains(builder.build())); + assertFalse(list.contains("package1", "attr1")); + assertTrue(list.contains("package2", "attr2")); + } + + @Test public void testWriteToParcel() { PackageTagsList list = new PackageTagsList.Builder() .add("package1", "attr1") diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java index 46e2772b30ca..90a9572b5560 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -44,6 +44,7 @@ import org.junit.runners.Suite; BatteryStatsUidTest.class, BatteryUsageStatsProviderTest.class, BatteryUsageStatsTest.class, + BatteryUsageStatsStoreTest.class, BatteryStatsUserLifecycleTests.class, BluetoothPowerCalculatorTest.class, BstatsCpuTimesValidationTest.class, diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java index d83645d6e0a5..cbd67c8324f4 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java @@ -20,10 +20,14 @@ import static com.google.common.truth.Truth.assertThat; import android.app.ActivityManager; import android.content.Context; +import android.os.BatteryConsumer; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.Parcel; import android.os.Process; import android.os.UidBatteryConsumer; @@ -36,6 +40,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.File; import java.util.List; @SmallTest @@ -45,7 +50,8 @@ public class BatteryUsageStatsProviderTest { private static final long MINUTE_IN_MS = 60 * 1000; @Rule - public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(12345); + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(12345) + .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0); @Test public void test_getBatteryUsageStats() { @@ -187,4 +193,84 @@ public class BatteryUsageStatsProviderTest { mStatsRule.setTime(11500, 0); assertThat(provider.shouldUpdateStats(queries, 10000)).isTrue(); } + + @Test + public void testAggregateBatteryStats() { + Context context = InstrumentationRegistry.getContext(); + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + mStatsRule.setCurrentTime(5 * MINUTE_IN_MS); + batteryStats.resetAllStatsCmdLocked(); + + BatteryUsageStatsStore batteryUsageStatsStore = new BatteryUsageStatsStore(context, + batteryStats, new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"), + new TestHandler(), Integer.MAX_VALUE); + + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, + batteryStats, batteryUsageStatsStore); + + batteryStats.noteFlashlightOnLocked(APP_UID, + 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS); + batteryStats.noteFlashlightOffLocked(APP_UID, + 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); + mStatsRule.setCurrentTime(25 * MINUTE_IN_MS); + batteryStats.resetAllStatsCmdLocked(); + + batteryStats.noteFlashlightOnLocked(APP_UID, + 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); + batteryStats.noteFlashlightOffLocked(APP_UID, + 50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS); + mStatsRule.setCurrentTime(55 * MINUTE_IN_MS); + batteryStats.resetAllStatsCmdLocked(); + + // This section should be ignored because the timestamp is out or range + batteryStats.noteFlashlightOnLocked(APP_UID, + 60 * MINUTE_IN_MS, 60 * MINUTE_IN_MS); + batteryStats.noteFlashlightOffLocked(APP_UID, + 70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS); + mStatsRule.setCurrentTime(75 * MINUTE_IN_MS); + batteryStats.resetAllStatsCmdLocked(); + + // This section should be ignored because it represents the current stats session + batteryStats.noteFlashlightOnLocked(APP_UID, + 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); + batteryStats.noteFlashlightOffLocked(APP_UID, + 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS); + mStatsRule.setCurrentTime(95 * MINUTE_IN_MS); + + // Include the first and the second snapshot, but not the third or current + BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() + .aggregateSnapshots(20 * MINUTE_IN_MS, 60 * MINUTE_IN_MS) + .build(); + final BatteryUsageStats stats = provider.getBatteryUsageStats(query); + + assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS); + assertThat(stats.getStatsEndTimestamp()).isEqualTo(55 * MINUTE_IN_MS); + assertThat(stats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isWithin(0.0001) + .of(180.0); // 360 mA * 0.5 hours + assertThat(stats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isEqualTo((10 + 20) * MINUTE_IN_MS); + final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream() + .filter(uid -> uid.getUid() == APP_UID).findFirst().get(); + assertThat(uidBatteryConsumer + .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isWithin(0.1) + .of(180.0); + } + + private static class TestHandler extends Handler { + TestHandler() { + super(Looper.getMainLooper()); + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + msg.getCallback().run(); + return true; + } + } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java new file mode 100644 index 000000000000..141a9fa30c85 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2021 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.internal.os; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.os.BatteryManager; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.TypedXmlSerializer; +import android.util.Xml; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +public class BatteryUsageStatsStoreTest { + private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024; + + private final MockClocks mMockClocks = new MockClocks(); + private MockBatteryStatsImpl mBatteryStats; + private BatteryUsageStatsStore mBatteryUsageStatsStore; + private BatteryUsageStatsProvider mBatteryUsageStatsProvider; + private File mStoreDirectory; + + @Before + public void setup() { + mMockClocks.currentTime = 123; + mBatteryStats = new MockBatteryStatsImpl(mMockClocks); + mBatteryStats.setNoAutoReset(true); + mBatteryStats.setPowerProfile(mock(PowerProfile.class)); + + Context context = InstrumentationRegistry.getContext(); + + mStoreDirectory = new File(context.getCacheDir(), "BatteryUsageStatsStoreTest"); + clearDirectory(mStoreDirectory); + + mBatteryUsageStatsStore = new BatteryUsageStatsStore(context, mBatteryStats, + mStoreDirectory, new TestHandler(), MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES); + + mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats); + } + + @Test + public void testStoreSnapshot() { + mMockClocks.currentTime = 1_600_000; + + prepareBatteryStats(); + mBatteryStats.resetAllStatsCmdLocked(); + + final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps(); + assertThat(timestamps).hasLength(1); + assertThat(timestamps[0]).isEqualTo(1_600_000); + + final BatteryUsageStats batteryUsageStats = mBatteryUsageStatsStore.loadBatteryUsageStats( + 1_600_000); + assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(123); + assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(1_600_000); + assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000); + assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(5); + assertThat(batteryUsageStats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE).getConsumedPower()) + .isEqualTo(600); // (3_600_000 - 3_000_000) / 1000 + } + + @Test + public void testGarbageCollectOldSnapshots() throws Exception { + prepareBatteryStats(); + + mMockClocks.realtime = 10_000_000; + mMockClocks.uptime = 10_000_000; + mMockClocks.currentTime = 10_000_000; + + final int snapshotFileSize = getSnapshotFileSize(); + final int numberOfSnapshots = + (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / snapshotFileSize); + for (int i = 0; i < numberOfSnapshots + 2; i++) { + mBatteryStats.resetAllStatsCmdLocked(); + + mMockClocks.realtime += 10_000_000; + mMockClocks.uptime += 10_000_000; + mMockClocks.currentTime += 10_000_000; + prepareBatteryStats(); + } + + final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps(); + Arrays.sort(timestamps); + assertThat(timestamps).hasLength(numberOfSnapshots); + // Two snapshots (10_000_000 and 20_000_000) should have been discarded + assertThat(timestamps[0]).isEqualTo(30_000_000); + assertThat(getDirectorySize(mStoreDirectory)) + .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES); + } + + @Test + public void testSavingStatsdAtomPullTimestamp() { + mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234); + assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp()) + .isEqualTo(1234); + mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478); + assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp()) + .isEqualTo(5478); + } + + private void prepareBatteryStats() { + mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100, + /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, + mMockClocks.realtime, mMockClocks.uptime, mMockClocks.currentTime); + mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100, + /* plugType */ 0, 85, 72, 3700, 3_000_000, 4_000_000, 0, + mMockClocks.realtime + 500_000, mMockClocks.uptime + 500_000, + mMockClocks.currentTime + 500_000); + } + + private void clearDirectory(File dir) { + if (dir.exists()) { + for (File child : dir.listFiles()) { + if (child.isDirectory()) { + clearDirectory(child); + } + child.delete(); + } + } + } + + private long getDirectorySize(File dir) { + long size = 0; + if (dir.exists()) { + for (File child : dir.listFiles()) { + if (child.isDirectory()) { + size += getDirectorySize(child); + } else { + size += child.length(); + } + } + } + return size; + } + + private int getSnapshotFileSize() throws IOException { + BatteryUsageStats stats = mBatteryUsageStatsProvider.getBatteryUsageStats( + new BatteryUsageStatsQuery.Builder() + .setMaxStatsAgeMs(0) + .includePowerModels() + .build()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TypedXmlSerializer serializer = Xml.newBinarySerializer(); + serializer.setOutput(out, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + stats.writeXml(serializer); + serializer.endDocument(); + return out.toByteArray().length; + } + + private static class TestHandler extends Handler { + TestHandler() { + super(Looper.getMainLooper()); + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + msg.getCallback().run(); + return true; + } + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java index 380b4ae7e748..3e620c2bbec6 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java @@ -16,8 +16,13 @@ package com.android.internal.os; +import static android.os.BatteryConsumer.POWER_MODEL_MEASURED_ENERGY; +import static android.os.BatteryConsumer.POWER_MODEL_POWER_PROFILE; +import static android.os.BatteryConsumer.POWER_MODEL_UNDEFINED; + import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import android.os.BatteryConsumer; @@ -25,6 +30,9 @@ import android.os.BatteryUsageStats; import android.os.Parcel; import android.os.UidBatteryConsumer; import android.os.UserBatteryConsumer; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import android.util.Xml; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -32,8 +40,11 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -42,15 +53,19 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) public class BatteryUsageStatsTest { + private static final int USER_ID = 42; + private static final int APP_UID1 = 271; + private static final int APP_UID2 = 314; + @Test public void testBuilder() { - BatteryUsageStats batteryUsageStats = buildBatteryUsageStats().build(); - validateBatteryUsageStats(batteryUsageStats); + BatteryUsageStats batteryUsageStats = buildBatteryUsageStats1(true).build(); + assertBatteryUsageStats1(batteryUsageStats, true); } @Test public void testParcelability() { - final BatteryUsageStats outBatteryUsageStats = buildBatteryUsageStats().build(); + final BatteryUsageStats outBatteryUsageStats = buildBatteryUsageStats1(true).build(); final Parcel outParcel = Parcel.obtain(); outParcel.writeParcelable(outBatteryUsageStats, 0); final byte[] bytes = outParcel.marshall(); @@ -62,20 +77,20 @@ public class BatteryUsageStatsTest { final BatteryUsageStats inBatteryUsageStats = inParcel.readParcelable(getClass().getClassLoader()); assertThat(inBatteryUsageStats).isNotNull(); - validateBatteryUsageStats(inBatteryUsageStats); + assertBatteryUsageStats1(inBatteryUsageStats, true); } @Test public void testDefaultSessionDuration() { final BatteryUsageStats stats = - buildBatteryUsageStats().setStatsDuration(10000).build(); + buildBatteryUsageStats1(true).setStatsDuration(10000).build(); assertThat(stats.getStatsDuration()).isEqualTo(10000); } @Test public void testDump() { - final BatteryUsageStats stats = buildBatteryUsageStats().build(); + final BatteryUsageStats stats = buildBatteryUsageStats1(true).build(); final StringWriter out = new StringWriter(); try (PrintWriter pw = new PrintWriter(out)) { stats.dump(pw, " "); @@ -87,7 +102,7 @@ public class BatteryUsageStatsTest { assertThat(dump).contains("actual drain: 1000-2000"); assertThat(dump).contains("cpu: 20100 apps: 10100 duration: 20s 300ms"); assertThat(dump).contains("FOO: 20200 apps: 10200 duration: 20s 400ms"); - assertThat(dump).contains("UID 2000: 1200 ( screen=300 cpu=400 FOO=500 )"); + assertThat(dump).contains("UID 271: 1200 ( screen=300 cpu=400 FOO=500 )"); assertThat(dump).contains("User 42: 30.0 ( cpu=10.0 FOO=20.0 )"); } @@ -101,154 +116,297 @@ public class BatteryUsageStatsTest { assertThat(allNames).hasSize(BatteryConsumer.POWER_COMPONENT_COUNT); } - private BatteryUsageStats.Builder buildBatteryUsageStats() { + @Test + public void testAdd() { + final BatteryUsageStats stats1 = buildBatteryUsageStats1(false).build(); + final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[] {"FOO"}).build(); + + final BatteryUsageStats sum = + new BatteryUsageStats.Builder(new String[] {"FOO"}, true) + .add(stats1) + .add(stats2) + .build(); + + assertBatteryUsageStats(sum, 42345, 50, 2234, 4345, 1000, 5000, 5000); + + final List<UidBatteryConsumer> uidBatteryConsumers = + sum.getUidBatteryConsumers(); + for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) { + if (uidBatteryConsumer.getUid() == APP_UID1) { + assertUidBatteryConsumer(uidBatteryConsumer, 2124, null, + 5321, 7432, 423, POWER_MODEL_POWER_PROFILE, 745, POWER_MODEL_UNDEFINED, + 956, 1167, 1478); + } else if (uidBatteryConsumer.getUid() == APP_UID2) { + assertUidBatteryConsumer(uidBatteryConsumer, 1332, "bar", + 1111, 2222, 333, POWER_MODEL_POWER_PROFILE, 444, POWER_MODEL_POWER_PROFILE, + 555, 666, 777); + } else { + fail("Unexpected UID " + uidBatteryConsumer.getUid()); + } + } + + assertAggregateBatteryConsumer(sum, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, + 20223, 20434, 20645, 20856); + + assertAggregateBatteryConsumer(sum, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, + 40211, 40422, 40633, 40844); + } + + @Test + public void testAdd_customComponentMismatch() { + final BatteryUsageStats.Builder builder = + new BatteryUsageStats.Builder(new String[] {"FOO"}, true); + final BatteryUsageStats stats = buildBatteryUsageStats2(new String[] {"BAR"}).build(); + + assertThrows(IllegalArgumentException.class, () -> builder.add(stats)); + } + + @Test + public void testXml() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TypedXmlSerializer serializer = Xml.newBinarySerializer(); + serializer.setOutput(out, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + final BatteryUsageStats stats = buildBatteryUsageStats1(true).build(); + stats.writeXml(serializer); + serializer.endDocument(); + + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + TypedXmlPullParser parser = Xml.newBinaryPullParser(); + parser.setInput(in, StandardCharsets.UTF_8.name()); + final BatteryUsageStats fromXml = BatteryUsageStats.createFromXml(parser); + + assertBatteryUsageStats1(fromXml, true); + } + + private BatteryUsageStats.Builder buildBatteryUsageStats1(boolean includeUserBatteryConsumer) { final MockClocks clocks = new MockClocks(); final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks); - final BatteryStatsImpl.Uid batteryStatsUid = batteryStats.getUidStatsLocked(2000); final BatteryUsageStats.Builder builder = - new BatteryUsageStats.Builder(new String[]{"FOO"}, true) + new BatteryUsageStats.Builder(new String[] {"FOO"}, true) .setBatteryCapacity(4000) .setDischargePercentage(20) .setDischargedPowerRange(1000, 2000) .setStatsStartTimestamp(1000) .setStatsEndTimestamp(3000); - builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid) - .setPackageWithHighestDrain("foo") - .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, 1000) - .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, 2000) - .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_SCREEN, 300) - .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU, 400) - .setConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 500) - .setUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU, 600) - .setUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 800); - builder.getAggregateBatteryConsumerBuilder( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) - .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU, 10100) - .setConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200) - .setUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU, 10300) - .setUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400); + addUidBatteryConsumer(builder, batteryStats, APP_UID1, "foo", + 1000, 2000, + 300, POWER_MODEL_POWER_PROFILE, 400, POWER_MODEL_POWER_PROFILE, 500, 600, 800); + + addAggregateBatteryConsumer(builder, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, 0, + 10100, 10200, 10300, 10400); + + addAggregateBatteryConsumer(builder, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, 30000, + 20100, 20200, 20300, 20400); + + + if (includeUserBatteryConsumer) { + builder.getOrCreateUserBatteryConsumerBuilder(USER_ID) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_CPU, 10) + .setConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20) + .setUsageDurationMillis( + BatteryConsumer.POWER_COMPONENT_CPU, 30) + .setUsageDurationForCustomComponentMillis( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 40); + } + return builder; + } + + private BatteryUsageStats.Builder buildBatteryUsageStats2(String[] customPowerComponentNames) { + final MockClocks clocks = new MockClocks(); + final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks); + + final BatteryUsageStats.Builder builder = + new BatteryUsageStats.Builder(customPowerComponentNames, true) + .setDischargePercentage(30) + .setDischargedPowerRange(1234, 2345) + .setStatsStartTimestamp(2000) + .setStatsEndTimestamp(5000); - builder.getAggregateBatteryConsumerBuilder( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) - .setConsumedPower(30000) + addUidBatteryConsumer(builder, batteryStats, APP_UID1, null, + 4321, 5432, + 123, POWER_MODEL_POWER_PROFILE, 345, POWER_MODEL_MEASURED_ENERGY, 456, 567, 678); + + addUidBatteryConsumer(builder, batteryStats, APP_UID2, "bar", + 1111, 2222, + 333, POWER_MODEL_POWER_PROFILE, 444, POWER_MODEL_POWER_PROFILE, 555, 666, 777); + + addAggregateBatteryConsumer(builder, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, 0, + 10123, 10234, 10345, 10456); + + addAggregateBatteryConsumer(builder, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, 12345, + 20111, 20222, 20333, 20444); + + return builder; + } + + private void addUidBatteryConsumer(BatteryUsageStats.Builder builder, + MockBatteryStatsImpl batteryStats, int uid, String packageWithHighestDrain, + int timeInStateForeground, int timeInStateBackground, int screenPower, + int screenPowerModel, int cpuPower, int cpuPowerModel, int customComponentPower, + int cpuDuration, int customComponentDuration) { + final BatteryStatsImpl.Uid batteryStatsUid = batteryStats.getUidStatsLocked(uid); + builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid) + .setPackageWithHighestDrain(packageWithHighestDrain) + .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, timeInStateForeground) + .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, timeInStateBackground) .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU, 20100) + BatteryConsumer.POWER_COMPONENT_SCREEN, screenPower, screenPowerModel) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_CPU, cpuPower, cpuPowerModel) .setConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20200) + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentPower) .setUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU, 20300) + BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration) .setUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20400); + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration); + } - builder.getOrCreateUserBatteryConsumerBuilder(42) + private void addAggregateBatteryConsumer(BatteryUsageStats.Builder builder, int scope, + double consumedPower, int cpuPower, int customComponentPower, int cpuDuration, + int customComponentDuration) { + builder.getAggregateBatteryConsumerBuilder(scope) + .setConsumedPower(consumedPower) .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU, 10) + BatteryConsumer.POWER_COMPONENT_CPU, cpuPower) .setConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20) + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentPower) .setUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU, 30) + BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration) .setUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 40); - - return builder; + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration); } - public void validateBatteryUsageStats(BatteryUsageStats batteryUsageStats) { - assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(30000); - assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000); - assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(20); - assertThat(batteryUsageStats.getDischargedPowerRange().getLower()).isEqualTo(1000); - assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()).isEqualTo(2000); - assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(1000); - assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(3000); - assertThat(batteryUsageStats.getStatsDuration()).isEqualTo(2000); + public void assertBatteryUsageStats1(BatteryUsageStats batteryUsageStats, + boolean includesUserBatteryConsumers) { + assertBatteryUsageStats(batteryUsageStats, 30000, 20, 1000, 2000, 1000, 3000, 2000); final List<UidBatteryConsumer> uidBatteryConsumers = batteryUsageStats.getUidBatteryConsumers(); + assertThat(uidBatteryConsumers).hasSize(1); for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) { - if (uidBatteryConsumer.getUid() == 2000) { - assertThat(uidBatteryConsumer.getPackageWithHighestDrain()).isEqualTo("foo"); - assertThat(uidBatteryConsumer.getTimeInStateMs( - UidBatteryConsumer.STATE_FOREGROUND)).isEqualTo(1000); - assertThat(uidBatteryConsumer.getTimeInStateMs( - UidBatteryConsumer.STATE_BACKGROUND)).isEqualTo(2000); - assertThat(uidBatteryConsumer.getConsumedPower( - BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(300); - assertThat(uidBatteryConsumer.getConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(400); - assertThat(uidBatteryConsumer.getConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(500); - assertThat(uidBatteryConsumer.getUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(600); - assertThat(uidBatteryConsumer.getUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(800); - assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(1200); - assertThat(uidBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1); - assertThat(uidBatteryConsumer.getCustomPowerComponentName( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO"); + if (uidBatteryConsumer.getUid() == APP_UID1) { + assertUidBatteryConsumer(uidBatteryConsumer, 1200, "foo", + 1000, 2000, 300, POWER_MODEL_POWER_PROFILE, 400, POWER_MODEL_POWER_PROFILE, + 500, 600, 800); } else { fail("Unexpected UID " + uidBatteryConsumer.getUid()); } } + final List<UserBatteryConsumer> userBatteryConsumers = + batteryUsageStats.getUserBatteryConsumers(); + if (includesUserBatteryConsumers) { + assertThat(userBatteryConsumers).hasSize(1); + for (UserBatteryConsumer userBatteryConsumer : userBatteryConsumers) { + if (userBatteryConsumer.getUserId() == USER_ID) { + assertUserBatteryConsumer(userBatteryConsumer, 42, 10, 20, 30, 40); + } else { + fail("Unexpected User ID " + userBatteryConsumer.getUserId()); + } + } + } else { + assertThat(userBatteryConsumers).isEmpty(); + } + + assertAggregateBatteryConsumer(batteryUsageStats, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, + 10100, 10200, 10300, 10400); + + assertAggregateBatteryConsumer(batteryUsageStats, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, + 20100, 20200, 20300, 20400); + } + + private void assertBatteryUsageStats(BatteryUsageStats batteryUsageStats, int consumedPower, + int dischargePercentage, int dischagePowerLower, int dischargePowerUpper, + int statsStartTimestamp, int statsEndTimestamp, int statsDuration) { + assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(consumedPower); + assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(dischargePercentage); + assertThat(batteryUsageStats.getDischargedPowerRange().getLower()).isEqualTo( + dischagePowerLower); + assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()).isEqualTo( + dischargePowerUpper); + assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(statsStartTimestamp); + assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(statsEndTimestamp); + assertThat(batteryUsageStats.getStatsDuration()).isEqualTo(statsDuration); + } + + private void assertUidBatteryConsumer(UidBatteryConsumer uidBatteryConsumer, + int consumedPower, String packageWithHighestDrain, int timeInStateForeground, + int timeInStateBackground, int screenPower, int screenPowerModel, int cpuPower, + int cpuPowerModel, int customComponentPower, int cpuDuration, + int customComponentDuration) { + assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(consumedPower); + assertThat(uidBatteryConsumer.getPackageWithHighestDrain()).isEqualTo( + packageWithHighestDrain); + assertThat(uidBatteryConsumer.getTimeInStateMs( + UidBatteryConsumer.STATE_FOREGROUND)).isEqualTo(timeInStateForeground); + assertThat(uidBatteryConsumer.getTimeInStateMs( + UidBatteryConsumer.STATE_BACKGROUND)).isEqualTo(timeInStateBackground); + assertThat(uidBatteryConsumer.getConsumedPower( + BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(screenPower); + assertThat(uidBatteryConsumer.getPowerModel( + BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(screenPowerModel); + assertThat(uidBatteryConsumer.getConsumedPower( + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPower); + assertThat(uidBatteryConsumer.getPowerModel( + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPowerModel); + assertThat(uidBatteryConsumer.getConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(customComponentPower); + assertThat(uidBatteryConsumer.getUsageDurationMillis( + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuDuration); + assertThat(uidBatteryConsumer.getUsageDurationForCustomComponentMillis( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo( + customComponentDuration); + assertThat(uidBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1); + assertThat(uidBatteryConsumer.getCustomPowerComponentName( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO"); + } + + private void assertUserBatteryConsumer(UserBatteryConsumer userBatteryConsumer, + int userId, int cpuPower, int customComponentPower, + int cpuDuration, int customComponentDuration) { + assertThat(userBatteryConsumer.getConsumedPower( + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPower); + assertThat(userBatteryConsumer.getConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(customComponentPower); + assertThat(userBatteryConsumer.getUsageDurationMillis( + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuDuration); + assertThat(userBatteryConsumer.getUsageDurationForCustomComponentMillis( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo( + customComponentDuration); + assertThat(userBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1); + assertThat(userBatteryConsumer.getCustomPowerComponentName( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO"); + } + + private void assertAggregateBatteryConsumer(BatteryUsageStats batteryUsageStats, + int aggregateBatteryConsumerScopeAllApps, int cpuPower, int customComponentPower, + int cpuDuration, int customComponentDuration) { final BatteryConsumer appsBatteryConsumer = batteryUsageStats.getAggregateBatteryConsumer( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS); + aggregateBatteryConsumerScopeAllApps); assertThat(appsBatteryConsumer.getConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10100); + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPower); assertThat(appsBatteryConsumer.getConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(10200); + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(customComponentPower); assertThat(appsBatteryConsumer.getUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10300); + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuDuration); assertThat(appsBatteryConsumer.getUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(10400); + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo( + customComponentDuration); assertThat(appsBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1); assertThat(appsBatteryConsumer.getCustomPowerComponentName( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO"); - - final BatteryConsumer deviceBatteryConsumer = batteryUsageStats.getAggregateBatteryConsumer( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); - assertThat(deviceBatteryConsumer.getConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(20100); - assertThat(deviceBatteryConsumer.getConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(20200); - assertThat(deviceBatteryConsumer.getUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(20300); - assertThat(deviceBatteryConsumer.getUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(20400); - assertThat(deviceBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1); - assertThat(deviceBatteryConsumer.getCustomPowerComponentName( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO"); - - final List<UserBatteryConsumer> userBatteryConsumers = - batteryUsageStats.getUserBatteryConsumers(); - for (UserBatteryConsumer userBatteryConsumer : userBatteryConsumers) { - if (userBatteryConsumer.getUserId() == 42) { - assertThat(userBatteryConsumer.getConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10); - assertThat(userBatteryConsumer.getConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(20); - assertThat(userBatteryConsumer.getUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(30); - assertThat(userBatteryConsumer.getUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(40); - assertThat(userBatteryConsumer.getConsumedPower()).isEqualTo(30); - assertThat(userBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1); - assertThat(userBatteryConsumer.getCustomPowerComponentName( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO"); - } else { - fail("Unexpected user ID " + userBatteryConsumer.getUserId()); - } - } } } diff --git a/data/etc/car/com.android.car.cluster.home.xml b/data/etc/car/com.android.car.cluster.home.xml index e1d2b18d7167..a3d0fcffc813 100644 --- a/data/etc/car/com.android.car.cluster.home.xml +++ b/data/etc/car/com.android.car.cluster.home.xml @@ -18,5 +18,6 @@ <privapp-permissions package="com.android.car.cluster.home"> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/> + <permission name="android.car.permission.CAR_MONITOR_INPUT"/> </privapp-permissions> </permissions> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 3c9086dde021..ac5e2d0fcacb 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -961,6 +961,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, + "-1003678883": { + "message": "Cleaning splash screen token=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_STARTING_WINDOW", + "at": "com\/android\/server\/wm\/ActivityRecord.java" + }, "-1003060523": { "message": "Finish needs to pause: %s", "level": "VERBOSE", diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index 954d062b55e9..6aa74cb415f9 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -753,8 +753,12 @@ public class HardwareRenderer { nCancelLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater()); } + private ASurfaceTransactionCallback mASurfaceTransactionCallback; + /** @hide */ public void setASurfaceTransactionCallback(ASurfaceTransactionCallback callback) { + // ensure callback is kept alive on the java side since weak ref is used in native code + mASurfaceTransactionCallback = callback; nSetASurfaceTransactionCallback(mNativeProxy, callback); } diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index fe80b5845bf5..1651a8cdcad5 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -221,6 +221,7 @@ public class RippleDrawable extends LayerDrawable { private boolean mForceSoftware; // Patterned + private boolean mAddRipple = false; private float mTargetBackgroundOpacity; private ValueAnimator mBackgroundAnimation; private float mBackgroundOpacity; @@ -716,6 +717,7 @@ public class RippleDrawable extends LayerDrawable { } cancelExitingRipples(); + exitPatternedAnimation(); } @Override @@ -807,7 +809,7 @@ public class RippleDrawable extends LayerDrawable { } private void startPatternedAnimation() { - mRippleActive = true; + mAddRipple = true; invalidateSelf(false); } @@ -862,17 +864,17 @@ public class RippleDrawable extends LayerDrawable { h = bounds.height(); w = bounds.width(); } - boolean shouldAnimate = mRippleActive; + boolean addRipple = mAddRipple; boolean shouldExit = mExitingAnimation; - mRippleActive = false; mExitingAnimation = false; - if (mRunningAnimations.size() > 0 && !shouldAnimate) { + mAddRipple = false; + if (mRunningAnimations.size() > 0 && !addRipple) { // update paint when view is invalidated getRipplePaint(); } drawContent(canvas); drawPatternedBackground(canvas, cx, cy); - if (shouldAnimate && mRunningAnimations.size() <= MAX_RIPPLES) { + if (addRipple && mRunningAnimations.size() <= MAX_RIPPLES) { RippleAnimationSession.AnimationProperties<Float, Paint> properties = createAnimationProperties(x, y, cx, cy, w, h); mRunningAnimations.add(new RippleAnimationSession(properties, !useCanvasProps) diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 4c2863e4f594..8cea869aea34 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -40,7 +40,7 @@ <integer name="long_press_dock_anim_duration">250</integer> <!-- Animation duration for translating of one handed when trigger / dismiss. --> - <integer name="config_one_handed_translate_animation_duration">800</integer> + <integer name="config_one_handed_translate_animation_duration">600</integer> <!-- One handed mode default offset % of display size --> <fraction name="config_one_handed_offset">40%</fraction> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index f7fb63d9ab98..4b1955e56a6c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -325,6 +325,13 @@ public class ShellTaskOrganizer extends TaskOrganizer { } @Override + public void onAppSplashScreenViewRemoved(int taskId) { + if (mStartingWindow != null) { + mStartingWindow.onAppSplashScreenViewRemoved(taskId); + } + } + + @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { synchronized (mLock) { onTaskAppeared(new TaskAppearedInfo(taskInfo, leash)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java index 8dc05de9bb8f..a525c2c0219c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java @@ -68,6 +68,11 @@ public interface OneHanded { void setLockedDisabled(boolean locked, boolean enabled); /** + * Registers callback to notify WMShell when user tap shortcut to expand notification. + */ + void registerEventCallback(OneHandedEventCallback callback); + + /** * Registers callback to be notified after {@link OneHandedDisplayAreaOrganizer} * transition start or finish */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index c275d50a5d56..b43daa0da2c0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -43,6 +43,7 @@ import android.util.Slog; import android.view.Surface; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -72,6 +73,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> private static final String ONE_HANDED_MODE_GESTURAL_OVERLAY = "com.android.internal.systemui.onehanded.gestural"; private static final int OVERLAY_ENABLED_DELAY_MS = 250; + private static final int DISPLAY_AREA_READY_RETRY_MS = 10; static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode"; @@ -99,6 +101,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> private final Handler mMainHandler; private final OneHandedImpl mImpl = new OneHandedImpl(); + private OneHandedEventCallback mEventCallback; private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer; private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer; @@ -288,7 +291,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> mTimeoutObserver = getObserver(this::onTimeoutSettingChanged); mTaskChangeExitObserver = getObserver(this::onTaskChangeExitSettingChanged); mSwipeToNotificationEnabledObserver = - getObserver(this::onSwipeToNotificationEnabledSettingChanged); + getObserver(this::onSwipeToNotificationEnabledChanged); mDisplayController.addDisplayChangingController(mRotationController); setupCallback(); @@ -358,14 +361,23 @@ public class OneHandedController implements RemoteCallable<OneHandedController> Slog.d(TAG, "Temporary lock disabled"); return; } + + if (!mDisplayAreaOrganizer.isReady()) { + // Must wait until DisplayAreaOrganizer is ready for transitioning. + mMainExecutor.executeDelayed(this::startOneHanded, DISPLAY_AREA_READY_RETRY_MS); + return; + } + if (mState.isTransitioning() || mState.isInOneHanded()) { return; } + final int currentRotation = mDisplayAreaOrganizer.getDisplayLayout().rotation(); if (currentRotation != Surface.ROTATION_0 && currentRotation != Surface.ROTATION_180) { Slog.w(TAG, "One handed mode only support portrait mode"); return; } + mState.setState(STATE_ENTERING); final int yOffSet = Math.round( mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction); @@ -394,6 +406,10 @@ public class OneHandedController implements RemoteCallable<OneHandedController> mOneHandedUiEventLogger.writeEvent(uiEvent); } + void registerEventCallback(OneHandedEventCallback callback) { + mEventCallback = callback; + } + @VisibleForTesting void registerTransitionCallback(OneHandedTransitionCallback callback) { mDisplayAreaOrganizer.registerTransitionCallback(callback); @@ -464,8 +480,29 @@ public class OneHandedController implements RemoteCallable<OneHandedController> } @VisibleForTesting + void notifyExpandNotification() { + mMainExecutor.execute(() -> mEventCallback.notifyExpandNotification()); + } + + @VisibleForTesting + void notifyUserConfigChanged(boolean success) { + if (!success) { + return; + } + // TODO Check UX if popup Toast to notify user when auto-enabled one-handed is good option. + Toast.makeText(mContext, R.string.one_handed_tutorial_title, Toast.LENGTH_LONG).show(); + } + + @VisibleForTesting void onActivatedActionChanged() { - if (mState.isTransitioning() || !isOneHandedEnabled()) { + if (!isOneHandedEnabled()) { + final boolean success = mOneHandedSettingsUtil.setOneHandedModeEnabled( + mContext.getContentResolver(), 1 /* Enabled for shortcut */, mUserId); + notifyUserConfigChanged(success); + } + + if (isSwipeToNotificationEnabled()) { + notifyExpandNotification(); return; } @@ -494,11 +531,9 @@ public class OneHandedController implements RemoteCallable<OneHandedController> setOneHandedEnabled(enabled); // Also checks swipe to notification settings since they all need gesture overlay. - // Enabled overlay package may affect the current animation(e.g:Settings switch), - // so we delay 250ms to enabled overlay after switch animation finish - mMainExecutor.executeDelayed(() -> setEnabledGesturalOverlay( + setEnabledGesturalOverlay( enabled || mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( - mContext.getContentResolver(), mUserId)), OVERLAY_ENABLED_DELAY_MS); + mContext.getContentResolver(), mUserId), true /* DelayExecute */); } @VisibleForTesting @@ -542,7 +577,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> } @VisibleForTesting - void onSwipeToNotificationEnabledSettingChanged() { + void onSwipeToNotificationEnabledChanged() { final boolean enabled = mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( mContext.getContentResolver(), mUserId); @@ -551,7 +586,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> // Also checks one handed mode settings since they all need gesture overlay. setEnabledGesturalOverlay( enabled || mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled( - mContext.getContentResolver(), mUserId)); + mContext.getContentResolver(), mUserId), true /* DelayExecute */); } private void setupTimeoutListener() { @@ -569,11 +604,19 @@ public class OneHandedController implements RemoteCallable<OneHandedController> return mIsOneHandedEnabled; } + @VisibleForTesting + boolean isSwipeToNotificationEnabled() { + return mIsSwipeToNotificationEnabled; + } + private void updateOneHandedEnabled() { if (mState.getState() == STATE_ENTERING || mState.getState() == STATE_ACTIVE) { mMainExecutor.execute(() -> stopOneHanded()); } + // Reset and align shortcut one_handed_mode_activated status with current mState + notifyShortcutState(mState.getState()); + mTouchHandler.onOneHandedEnabled(mIsOneHandedEnabled); if (!mIsOneHandedEnabled) { @@ -608,12 +651,19 @@ public class OneHandedController implements RemoteCallable<OneHandedController> if (info != null && !info.isEnabled()) { // Enable the default gestural one handed overlay. - setEnabledGesturalOverlay(true); + setEnabledGesturalOverlay(true /* enabled */, false /* delayExecute */); } } @VisibleForTesting - private void setEnabledGesturalOverlay(boolean enabled) { + private void setEnabledGesturalOverlay(boolean enabled, boolean delayExecute) { + if (mState.isTransitioning() || delayExecute) { + // Enabled overlay package may affect the current animation(e.g:Settings switch), + // so we delay 250ms to enabled overlay after switch animation finish, only delay once. + mMainExecutor.executeDelayed(() -> setEnabledGesturalOverlay(enabled, false), + OVERLAY_ENABLED_DELAY_MS); + return; + } try { mOverlayManager.setEnabled(ONE_HANDED_MODE_GESTURAL_OVERLAY, enabled, USER_CURRENT); } catch (RemoteException e) { @@ -628,6 +678,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> if (enabled == isFeatureEnabled) { return; } + mLockedDisabled = locked && !enabled; } @@ -761,6 +812,13 @@ public class OneHandedController implements RemoteCallable<OneHandedController> } @Override + public void registerEventCallback(OneHandedEventCallback callback) { + mMainExecutor.execute(() -> { + OneHandedController.this.registerEventCallback(callback); + }); + } + + @Override public void registerTransitionCallback(OneHandedTransitionCallback callback) { mMainExecutor.execute(() -> { OneHandedController.this.registerTransitionCallback(callback); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java index b8da37fd0c25..d749c320bf94 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java @@ -61,11 +61,12 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { private DisplayLayout mDisplayLayout = new DisplayLayout(); - private float mLastVisualOffset = 0; private final Rect mLastVisualDisplayBounds = new Rect(); private final Rect mDefaultDisplayBounds = new Rect(); private final OneHandedSettingsUtil mOneHandedSettingsUtil; + private boolean mIsReady; + private float mLastVisualOffset = 0; private int mEnterExitAnimationDurationMs; private ArrayMap<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap = new ArrayMap(); @@ -157,6 +158,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { final DisplayAreaAppearedInfo info = displayAreaInfos.get(i); onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash()); } + mIsReady = true; updateDisplayBounds(); return displayAreaInfos; } @@ -164,9 +166,14 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { @Override public void unregisterOrganizer() { super.unregisterOrganizer(); + mIsReady = false; resetWindowsOffset(); } + boolean isReady() { + return mIsReady; + } + /** * Handler for display rotation changes by {@link DisplayLayout} * @@ -312,6 +319,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { pw.println(mDisplayAreaTokenMap); pw.print(innerPrefix + "mDefaultDisplayBounds="); pw.println(mDefaultDisplayBounds); + pw.print(innerPrefix + "mIsReady="); + pw.println(mIsReady); pw.print(innerPrefix + "mLastVisualDisplayBounds="); pw.println(mLastVisualDisplayBounds); pw.print(innerPrefix + "mLastVisualOffset="); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedEventCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedEventCallback.java new file mode 100644 index 000000000000..d07eea271eac --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedEventCallback.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 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.wm.shell.onehanded; + +/** + * Additional callback interface for OneHanded events. + */ +public interface OneHandedEventCallback { + /** + * Called to notify expand notification shade. + */ + default void notifyExpandNotification() { + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java index 90fc823fb574..da53b359a304 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java @@ -105,6 +105,17 @@ public final class OneHandedSettingsUtil { } /** + * Sets one handed enable or disable flag from Settings provider. + * + * @return true if the value was set, false on database errors + */ + public boolean setOneHandedModeEnabled(ContentResolver resolver, int enabled, int userId) { + return Settings.Secure.putIntForUser(resolver, + Settings.Secure.ONE_HANDED_MODE_ENABLED, enabled, userId); + } + + + /** * Queries taps app to exit config from Settings provider. * * @return enable or disable taps app exit. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 841edef9172f..f0bd8a2846ed 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -235,15 +235,20 @@ public class PipResizeGestureHandler { @VisibleForTesting void onInputEvent(InputEvent ev) { + if (!mEnableDragCornerResize && !mEnablePinchResize) { + // No need to handle anything if neither form of resizing is enabled. + return; + } + // Don't allow resize when PiP is stashed. if (mPipBoundsState.isStashed()) { return; } if (ev instanceof MotionEvent) { - if (mOngoingPinchToResize) { + if (mEnablePinchResize && mOngoingPinchToResize) { onPinchResize((MotionEvent) ev); - } else { + } else if (mEnableDragCornerResize) { onDragCornerResize((MotionEvent) ev); } } @@ -318,8 +323,8 @@ public class PipResizeGestureHandler { case MotionEvent.ACTION_POINTER_DOWN: if (mEnablePinchResize && ev.getPointerCount() == 2) { onPinchResize(ev); - mOngoingPinchToResize = true; - return true; + mOngoingPinchToResize = mAllowGesture; + return mAllowGesture; } break; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 4d33cb0452dc..46db35a6e29f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -35,6 +35,7 @@ import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.hardware.display.DisplayManager; import android.os.IBinder; +import android.os.RemoteCallback; import android.os.Trace; import android.os.UserHandle; import android.util.Slog; @@ -42,6 +43,7 @@ import android.util.SparseArray; import android.view.Choreographer; import android.view.Display; import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.View; import android.view.WindowManager; import android.widget.FrameLayout; @@ -121,6 +123,13 @@ public class StartingSurfaceDrawer { private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>(); + /** + * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is + * rendered and that have not yet been removed by their client. + */ + private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts = + new SparseArray<>(1); + /** Obtain proper context for showing splash screen on the provided display. */ private Context getDisplayContext(Context context, int displayId) { if (displayId == DEFAULT_DISPLAY) { @@ -386,25 +395,58 @@ public class StartingSurfaceDrawer { /** * Called when the Task wants to copy the splash screen. - * @param taskId */ public void copySplashScreenView(int taskId) { final StartingWindowRecord preView = mStartingWindowRecords.get(taskId); SplashScreenViewParcelable parcelable; - if (preView != null && preView.mContentView != null - && preView.mContentView.isCopyable()) { - parcelable = new SplashScreenViewParcelable(preView.mContentView); - preView.mContentView.onCopied(); + SplashScreenView splashScreenView = preView != null ? preView.mContentView : null; + if (splashScreenView != null && splashScreenView.isCopyable()) { + parcelable = new SplashScreenViewParcelable(splashScreenView); + parcelable.setClientCallback( + new RemoteCallback((bundle) -> mSplashScreenExecutor.execute( + () -> onAppSplashScreenViewRemoved(taskId, false)))); + splashScreenView.onCopied(); + mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost()); } else { parcelable = null; } if (DEBUG_SPLASH_SCREEN) { Slog.v(TAG, "Copying splash screen window view for task: " + taskId - + " parcelable? " + parcelable); + + " parcelable: " + parcelable); } ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable); } + /** + * Called when the {@link SplashScreenView} is removed from the client Activity view's hierarchy + * or when the Activity is clean up. + * + * @param taskId The Task id on which the splash screen was attached + */ + public void onAppSplashScreenViewRemoved(int taskId) { + onAppSplashScreenViewRemoved(taskId, true /* fromServer */); + } + + /** + * @param fromServer If true, this means the removal was notified by the server. This is only + * used for debugging purposes. + * @see #onAppSplashScreenViewRemoved(int) + */ + private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) { + SurfaceControlViewHost viewHost = + mAnimatedSplashScreenSurfaceHosts.get(taskId); + if (viewHost == null) { + return; + } + mAnimatedSplashScreenSurfaceHosts.remove(taskId); + if (DEBUG_SPLASH_SCREEN) { + String reason = fromServer ? "Server cleaned up" : "App removed"; + Slog.v(TAG, reason + "the splash screen. Releasing SurfaceControlViewHost for task:" + + taskId); + } + viewHost.getView().post(viewHost::release); + } + protected boolean addWindow(int taskId, IBinder appToken, View view, WindowManager wm, WindowManager.LayoutParams params) { boolean shouldSaveView = true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java index cffc789106cb..9c1dde925762 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java @@ -150,6 +150,14 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo } /** + * @see StartingSurfaceDrawer#onAppSplashScreenViewRemoved(int) + */ + public void onAppSplashScreenViewRemoved(int taskId) { + mSplashScreenExecutor.execute( + () -> mStartingSurfaceDrawer.onAppSplashScreenViewRemoved(taskId)); + } + + /** * Called when the content of a task is ready to show, starting window can be removed. */ public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index 1852279ee96c..47789b7490ee 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -74,6 +74,8 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Mock OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer; @Mock + OneHandedEventCallback mMockEventCallback; + @Mock OneHandedTouchHandler mMockTouchHandler; @Mock OneHandedTutorialHandler mMockTutorialHandler; @@ -106,6 +108,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay); when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>()); + when(mMockDisplayAreaOrganizer.isReady()).thenReturn(true); when(mMockBackgroundOrganizer.getBackgroundSurface()).thenReturn(mMockLeash); when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn( mDefaultEnabled); @@ -241,7 +244,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Test public void testSettingsObserverUpdateSwipeToNotification() { - mSpiedOneHandedController.onSwipeToNotificationEnabledSettingChanged(); + mSpiedOneHandedController.onSwipeToNotificationEnabledChanged(); verify(mSpiedOneHandedController).setSwipeToNotificationEnabled(anyBoolean()); } @@ -311,6 +314,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { final DisplayLayout testDisplayLayout = new DisplayLayout(mDisplayLayout); testDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180); mSpiedTransitionState.setState(STATE_NONE); + when(mMockDisplayAreaOrganizer.isReady()).thenReturn(true); when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(testDisplayLayout); mSpiedOneHandedController.setOneHandedEnabled(true); mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */); @@ -372,8 +376,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(true); mSpiedOneHandedController.onActivatedActionChanged(); - verify(mSpiedOneHandedController, never()).startOneHanded(); - verify(mSpiedOneHandedController, never()).stopOneHanded(); + verify(mSpiedTransitionState, never()).setState(STATE_EXITING); } @Test @@ -383,20 +386,20 @@ public class OneHandedControllerTest extends OneHandedTestCase { when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(true); mSpiedOneHandedController.onActivatedActionChanged(); - verify(mSpiedOneHandedController, never()).startOneHanded(); - verify(mSpiedOneHandedController, never()).stopOneHanded(); + verify(mSpiedTransitionState, never()).setState(STATE_ENTERING); } @Test - public void testOneHandedDisabled_shortcutEnabled_skipActions() { + public void testOneHandedDisabled_shortcutTrigger_thenAutoEnabled() { when(mSpiedOneHandedController.isOneHandedEnabled()).thenReturn(false); when(mSpiedTransitionState.getState()).thenReturn(STATE_NONE); when(mSpiedTransitionState.isTransitioning()).thenReturn(false); - when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(true); + when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(false); + when(mMockSettingsUitl.setOneHandedModeEnabled(any(), anyInt(), anyInt())).thenReturn( + false); mSpiedOneHandedController.onActivatedActionChanged(); - verify(mSpiedOneHandedController, never()).startOneHanded(); - verify(mSpiedOneHandedController, never()).stopOneHanded(); + verify(mSpiedOneHandedController).notifyUserConfigChanged(anyBoolean()); } @Test @@ -408,4 +411,28 @@ public class OneHandedControllerTest extends OneHandedTestCase { verify(mSpiedTransitionState).addSListeners(mMockTutorialHandler); } + + @Test + public void testNotifyEventCallbackWithMainExecutor() { + when(mSpiedOneHandedController.isOneHandedEnabled()).thenReturn(true); + when(mSpiedTransitionState.getState()).thenReturn(STATE_NONE); + when(mSpiedTransitionState.isTransitioning()).thenReturn(false); + when(mSpiedOneHandedController.isSwipeToNotificationEnabled()).thenReturn(true); + mSpiedOneHandedController.registerEventCallback(mMockEventCallback); + mSpiedOneHandedController.onActivatedActionChanged(); + + verify(mMockShellMainExecutor).execute(any()); + } + + @Test + public void testNotifyShortcutState_whenUpdateOneHandedEnabled() { + when(mSpiedOneHandedController.isOneHandedEnabled()).thenReturn(false); + when(mSpiedTransitionState.getState()).thenReturn(STATE_NONE); + when(mSpiedTransitionState.isTransitioning()).thenReturn(false); + when(mSpiedOneHandedController.isSwipeToNotificationEnabled()).thenReturn(true); + mSpiedOneHandedController.registerEventCallback(mMockEventCallback); + mSpiedOneHandedController.setOneHandedEnabled(true); + + verify(mSpiedOneHandedController).notifyShortcutState(anyInt()); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java index a27ed114de70..ef16fd391235 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java @@ -418,4 +418,18 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset(); } + + @Test + public void testDisplayArea_notReadyForTransition() { + OneHandedDisplayAreaOrganizer testSpiedDisplayAreaOrganizer = spy( + new OneHandedDisplayAreaOrganizer(mContext, + mDisplayLayout, + mMockSettingsUitl, + mMockAnimationController, + mTutorialHandler, + mMockBackgroundOrganizer, + mMockShellMainExecutor)); + + assertThat(testSpiedDisplayAreaOrganizer.isReady()).isFalse(); + } } diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index 602c32a966d3..819a34b21a05 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -500,6 +500,28 @@ private: jobject mObject; }; +class JWeakGlobalRefHolder { +public: + JWeakGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm) { + mWeakRef = getenv(vm)->NewWeakGlobalRef(object); + } + + virtual ~JWeakGlobalRefHolder() { + if (mWeakRef != nullptr) getenv(mVm)->DeleteWeakGlobalRef(mWeakRef); + mWeakRef = nullptr; + } + + jobject ref() { return mWeakRef; } + JavaVM* vm() { return mVm; } + +private: + JWeakGlobalRefHolder(const JWeakGlobalRefHolder&) = delete; + void operator=(const JWeakGlobalRefHolder&) = delete; + + JavaVM* mVm; + jobject mWeakRef; +}; + using TextureMap = std::unordered_map<uint32_t, sk_sp<SkImage>>; struct PictureCaptureState { @@ -633,15 +655,19 @@ static void android_view_ThreadedRenderer_setASurfaceTransactionCallback( } else { JavaVM* vm = nullptr; LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); - auto globalCallbackRef = std::make_shared<JGlobalRefHolder>( - vm, env->NewGlobalRef(aSurfaceTransactionCallback)); + auto globalCallbackRef = + std::make_shared<JWeakGlobalRefHolder>(vm, aSurfaceTransactionCallback); proxy->setASurfaceTransactionCallback( [globalCallbackRef](int64_t transObj, int64_t scObj, int64_t frameNr) { JNIEnv* env = getenv(globalCallbackRef->vm()); - env->CallVoidMethod(globalCallbackRef->object(), - gASurfaceTransactionCallback.onMergeTransaction, + jobject localref = env->NewLocalRef(globalCallbackRef->ref()); + if (CC_UNLIKELY(!localref)) { + return; + } + env->CallVoidMethod(localref, gASurfaceTransactionCallback.onMergeTransaction, static_cast<jlong>(transObj), static_cast<jlong>(scObj), static_cast<jlong>(frameNr)); + env->DeleteLocalRef(localref); }); } } diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java index 763835c9cbe2..d59756d02348 100644 --- a/location/java/android/location/LocationManagerInternal.java +++ b/location/java/android/location/LocationManagerInternal.java @@ -16,14 +16,10 @@ package android.location; - import android.annotation.NonNull; import android.annotation.Nullable; import android.location.util.identity.CallerIdentity; - -import com.android.internal.annotations.Immutable; - -import java.util.Set; +import android.os.PackageTagsList; /** * Location manager local system service interface. @@ -43,18 +39,14 @@ public abstract class LocationManagerInternal { } /** - * Interface for getting callbacks when a location provider's location tags change. - * - * @see LocationTagInfo + * Interface for getting callbacks when an app id's location provider package tags change. */ - public interface OnProviderLocationTagsChangeListener { + public interface LocationPackageTagsListener { /** - * Called when the location tags for a provider change. - * - * @param providerLocationTagInfo The tag info for a provider. + * Called when the package tags for a location provider change for a uid. */ - void onLocationTagsChanged(@NonNull LocationTagInfo providerLocationTagInfo); + void onLocationPackageTagsChanged(int uid, @NonNull PackageTagsList packageTagsList); } /** @@ -109,58 +101,9 @@ public abstract class LocationManagerInternal { public abstract @Nullable LocationTime getGnssTimeMillis(); /** - * Sets a listener for changes in the location providers' tags. Passing + * Sets a listener for changes in an app id's location provider package tags. Passing * {@code null} clears the current listener. - * - * @param listener The listener. */ - public abstract void setOnProviderLocationTagsChangeListener( - @Nullable OnProviderLocationTagsChangeListener listener); - - /** - * This class represents the location permission tags used by the location provider - * packages in a given UID. These tags are strictly used for accessing state guarded - * by the location permission(s) by a location provider which are required for the - * provider to fulfill its function as being a location provider. - */ - @Immutable - public static class LocationTagInfo { - private final int mUid; - - @NonNull - private final String mPackageName; - - @Nullable - private final Set<String> mLocationTags; - - public LocationTagInfo(int uid, @NonNull String packageName, - @Nullable Set<String> locationTags) { - mUid = uid; - mPackageName = packageName; - mLocationTags = locationTags; - } - - /** - * @return The UID for which tags are related. - */ - public int getUid() { - return mUid; - } - - /** - * @return The package for which tags are related. - */ - @NonNull - public String getPackageName() { - return mPackageName; - } - - /** - * @return The tags for the package used for location related accesses. - */ - @Nullable - public Set<String> getTags() { - return mLocationTags; - } - } + public abstract void setLocationPackageTagsListener( + @Nullable LocationPackageTagsListener listener); } diff --git a/packages/CtsShim/apk/arm/CtsShim.apk b/packages/CtsShim/apk/arm/CtsShim.apk Binary files differindex bb6dfa3a4ede..18915039b6a1 100644 --- a/packages/CtsShim/apk/arm/CtsShim.apk +++ b/packages/CtsShim/apk/arm/CtsShim.apk diff --git a/packages/CtsShim/apk/arm/CtsShimPriv.apk b/packages/CtsShim/apk/arm/CtsShimPriv.apk Binary files differindex 2835d57474d9..eb2202586753 100644 --- a/packages/CtsShim/apk/arm/CtsShimPriv.apk +++ b/packages/CtsShim/apk/arm/CtsShimPriv.apk diff --git a/packages/CtsShim/apk/x86/CtsShim.apk b/packages/CtsShim/apk/x86/CtsShim.apk Binary files differindex bb6dfa3a4ede..18915039b6a1 100644 --- a/packages/CtsShim/apk/x86/CtsShim.apk +++ b/packages/CtsShim/apk/x86/CtsShim.apk diff --git a/packages/CtsShim/apk/x86/CtsShimPriv.apk b/packages/CtsShim/apk/x86/CtsShimPriv.apk Binary files differindex 2e1a78989b70..b3f923c4176f 100644 --- a/packages/CtsShim/apk/x86/CtsShimPriv.apk +++ b/packages/CtsShim/apk/x86/CtsShimPriv.apk diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java index a5373944474c..0b3a519cd919 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java @@ -16,8 +16,10 @@ package com.android.settingslib.enterprise; +import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.Context; +import android.content.DialogInterface; import androidx.annotation.Nullable; @@ -54,4 +56,13 @@ public interface ActionDisabledByAdminController { * Updates the enforced admin */ void updateEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin, @UserIdInt int adminUserId); + + /** + * Returns a listener for handling positive button clicks + */ + @Nullable + default DialogInterface.OnClickListener getPositiveButtonListener(@NonNull Context context, + @NonNull RestrictedLockUtils.EnforcedAdmin enforcedAdmin) { + return null; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java index da42e330b8b4..44cafb17e1d8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java @@ -20,6 +20,11 @@ import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import android.app.admin.DevicePolicyManager; import android.content.Context; +import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.ParentalControlsUtilsInternal; +import android.os.UserHandle; +import android.os.UserManager; +import android.text.TextUtils; /** * A factory that returns the relevant instance of {@link ActionDisabledByAdminController}. @@ -30,10 +35,28 @@ public final class ActionDisabledByAdminControllerFactory { * Returns the relevant instance of {@link ActionDisabledByAdminController}. */ public static ActionDisabledByAdminController createInstance(Context context, - DeviceAdminStringProvider stringProvider) { - return isFinancedDevice(context) - ? new FinancedDeviceActionDisabledByAdminController(stringProvider) - : new ManagedDeviceActionDisabledByAdminController(stringProvider); + String restriction, DeviceAdminStringProvider stringProvider) { + if (doesBiometricRequireParentalConsent(context, restriction)) { + return new BiometricActionDisabledByAdminController(stringProvider); + } else if (isFinancedDevice(context)) { + return new FinancedDeviceActionDisabledByAdminController(stringProvider); + } else { + return new ManagedDeviceActionDisabledByAdminController(stringProvider); + } + } + + /** + * @return true if the restriction == UserManager.DISALLOW_BIOMETRIC and parental consent + * is required. + */ + private static boolean doesBiometricRequireParentalConsent(Context context, + String restriction) { + if (!TextUtils.equals(UserManager.DISALLOW_BIOMETRIC, restriction)) { + return false; + } + DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); + return ParentalControlsUtilsInternal.parentConsentRequired(context, dpm, + BiometricAuthenticator.TYPE_ANY_BIOMETRIC, new UserHandle(UserHandle.myUserId())); } private static boolean isFinancedDevice(Context context) { diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java new file mode 100644 index 000000000000..814d5d23f458 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 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.settingslib.enterprise; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.util.Log; + +import androidx.annotation.Nullable; + +import com.android.settingslib.RestrictedLockUtils; + +public class BiometricActionDisabledByAdminController extends BaseActionDisabledByAdminController { + + private static final String TAG = "BiometricActionDisabledByAdminController"; + + // These MUST not change, as they are the stable API between here and device admin specified + // by the component below. + private static final String ACTION_LEARN_MORE = "android.settings.LEARN_MORE"; + private static final String EXTRA_FROM_BIOMETRIC_SETUP = "from_biometric_setup"; + + BiometricActionDisabledByAdminController( + DeviceAdminStringProvider stringProvider) { + super(stringProvider); + } + + @Override + public void setupLearnMoreButton(Context context) { + + } + + @Override + public String getAdminSupportTitle(@Nullable String restriction) { + return mStringProvider.getDisabledBiometricsParentConsentTitle(); + } + + @Override + public CharSequence getAdminSupportContentString(Context context, + @Nullable CharSequence supportMessage) { + return mStringProvider.getDisabledBiometricsParentConsentContent(); + } + + @Override + public DialogInterface.OnClickListener getPositiveButtonListener(@NonNull Context context, + @NonNull RestrictedLockUtils.EnforcedAdmin enforcedAdmin) { + return (dialog, which) -> { + Log.d(TAG, "Positive button clicked, component: " + enforcedAdmin.component); + final Intent intent = new Intent(ACTION_LEARN_MORE) + .setComponent(enforcedAdmin.component) + .putExtra(EXTRA_FROM_BIOMETRIC_SETUP, true) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + }; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/DeviceAdminStringProvider.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/DeviceAdminStringProvider.java index c47d789a514d..b83837e6caf6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/DeviceAdminStringProvider.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/DeviceAdminStringProvider.java @@ -72,4 +72,14 @@ public interface DeviceAdminStringProvider { * a financed device. */ String getDisabledByPolicyTitleForFinancedDevice(); + + /** + * Returns the dialog title for when biometrics require parental consent. + */ + String getDisabledBiometricsParentConsentTitle(); + + /** + * Returns the dialog contents for when biometrics require parental consent. + */ + String getDisabledBiometricsParentConsentContent(); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java new file mode 100644 index 000000000000..766c2f5f9872 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 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.settingslib.enterprise; + +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCED_ADMIN; +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCEMENT_ADMIN_USER_ID; +import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DEVICE_ADMIN_STRING_PROVIDER; + +import static junit.framework.Assert.assertNotNull; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.UserHandle; + +import com.android.settingslib.RestrictedLockUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class BiometricActionDisabledByAdminControllerTest { + + private final ActionDisabledByAdminControllerTestUtils mTestUtils = + new ActionDisabledByAdminControllerTestUtils(); + private final BiometricActionDisabledByAdminController mController = + new BiometricActionDisabledByAdminController(DEFAULT_DEVICE_ADMIN_STRING_PROVIDER); + + @Before + public void setUp() { + mController.initialize(mTestUtils.createLearnMoreButtonLauncher()); + mController.updateEnforcedAdmin(ENFORCED_ADMIN, ENFORCEMENT_ADMIN_USER_ID); + } + + @Test + public void buttonClicked() { + Context context = mock(Context.class); + ComponentName componentName = mock(ComponentName.class); + RestrictedLockUtils.EnforcedAdmin enforcedAdmin = new RestrictedLockUtils.EnforcedAdmin( + componentName, new UserHandle(UserHandle.myUserId())); + + DialogInterface.OnClickListener listener = + mController.getPositiveButtonListener(context, enforcedAdmin); + assertNotNull("Biometric Controller must supply a non-null listener", listener); + listener.onClick(mock(DialogInterface.class), 0 /* which */); + + ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(context).startActivity(intentCaptor.capture()); + assertEquals("android.settings.LEARN_MORE", + intentCaptor.getValue().getAction()); + assertTrue("from_biometric_setup", intentCaptor.getValue() + .getBooleanExtra("from_biometric_setup", false)); + assertEquals(componentName, intentCaptor.getValue().getComponent()); + } + +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java index be3e9fcf45ea..99e13c325472 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java @@ -30,6 +30,8 @@ class FakeDeviceAdminStringProvider implements DeviceAdminStringProvider { static final String DEFAULT_DISABLED_BY_POLICY_CONTENT = "default_disabled_by_policy_content"; static final String DEFAULT_DISABLED_BY_POLICY_TITLE_FINANCED_DEVICE = "default_disabled_by_policy_title_financed_device"; + static final String DEFAULT_BIOMETRIC_TITLE = "biometric_title"; + static final String DEFAULT_BIOMETRIC_CONTENTS = "biometric_contents"; static final DeviceAdminStringProvider DEFAULT_DEVICE_ADMIN_STRING_PROVIDER = new FakeDeviceAdminStringProvider(/* url = */ null); @@ -88,4 +90,15 @@ class FakeDeviceAdminStringProvider implements DeviceAdminStringProvider { public String getDisabledByPolicyTitleForFinancedDevice() { return DEFAULT_DISABLED_BY_POLICY_TITLE_FINANCED_DEVICE; } + + @Override + public String getDisabledBiometricsParentConsentTitle() { + return DEFAULT_BIOMETRIC_TITLE; + } + + @Override + public String getDisabledBiometricsParentConsentContent() { + return DEFAULT_BIOMETRIC_CONTENTS; + } + } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 8bc3d228f7a8..2579e7084e08 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -238,7 +238,9 @@ class ActivityLaunchAnimator( * during the animation. */ @JvmStatic - fun fromView(view: View): Controller = GhostedViewLaunchAnimatorController(view) + fun fromView(view: View, cujType: Int? = null): Controller { + return GhostedViewLaunchAnimatorController(view, cujType) + } } /** diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index 4b655a1a1b02..ffb7ab4eff7c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -14,6 +14,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewGroupOverlay import android.widget.FrameLayout +import com.android.internal.jank.InteractionJankMonitor import kotlin.math.min /** @@ -29,7 +30,10 @@ import kotlin.math.min */ open class GhostedViewLaunchAnimatorController( /** The view that will be ghosted and from which the background will be extracted. */ - private val ghostedView: View + private val ghostedView: View, + + /** The [InteractionJankMonitor.CujType] associated to this animation. */ + private val cujType: Int? = null ) : ActivityLaunchAnimator.Controller { /** The container to which we will add the ghost view and expanding background. */ override var launchContainer = ghostedView.rootView as ViewGroup @@ -125,6 +129,8 @@ open class GhostedViewLaunchAnimatorController( val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX matrix.getValues(initialGhostViewMatrixValues) + + cujType?.let { InteractionJankMonitor.getInstance().begin(ghostedView, it) } } override fun onLaunchAnimationProgress( @@ -167,6 +173,8 @@ open class GhostedViewLaunchAnimatorController( } override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + cujType?.let { InteractionJankMonitor.getInstance().end(it) } + backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha GhostView.removeGhost(ghostedView) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 298b7c38066b..7c81325d685f 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -92,5 +92,12 @@ public interface ActivityStarter { * *after* returning to start hiding the keyguard. */ boolean onDismiss(); + + /** + * Whether running this action when we are locked will start an animation on the keyguard. + */ + default boolean willRunAnimationOnKeyguard() { + return false; + } } } diff --git a/packages/SystemUI/res/layout/global_actions_change_panel.xml b/packages/SystemUI/res/layout/global_actions_change_panel.xml index dffb0f011bb5..bc9c203b299a 100644 --- a/packages/SystemUI/res/layout/global_actions_change_panel.xml +++ b/packages/SystemUI/res/layout/global_actions_change_panel.xml @@ -14,8 +14,18 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<ImageView +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/global_actions_change_button" android:layout_width="wrap_content" - android:layout_height="wrap_content"/> + android:layout_height="wrap_content"> + <TextView + android:id="@+id/global_actions_change_message" + android:layout_width="wrap_content" + android:visibility="gone" + android:layout_height="wrap_content" + android:text="@string/global_actions_change_description" /> + <ImageView + android:id="@+id/global_actions_change_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml index 4765b44d78c8..3f4baaf27b84 100644 --- a/packages/SystemUI/res/layout/long_screenshot.xml +++ b/packages/SystemUI/res/layout/long_screenshot.xml @@ -99,6 +99,7 @@ app:handleThickness="@dimen/screenshot_crop_handle_thickness" app:handleColor="?androidprv:attr/colorAccentPrimary" app:scrimColor="@color/screenshot_crop_scrim" + app:containerBackgroundColor="?android:colorBackgroundFloating" tools:background="?android:colorBackground" tools:minHeight="100dp" tools:minWidth="100dp" /> diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml index c88703dabeea..5b9ca1b26158 100644 --- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml +++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml @@ -59,11 +59,16 @@ android:visibility="gone" /> - <LinearLayout + <FrameLayout android:id="@+id/rightLayout" android:layout_width="wrap_content" android:layout_height="match_parent" - android:gravity="center_vertical|end" + android:gravity="end" + > + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="center_vertical|end" > <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons" @@ -80,4 +85,6 @@ android:paddingEnd="2dp" /> </LinearLayout> + </FrameLayout> + </LinearLayout> diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml index 70bd85036198..2f0957caaaae 100644 --- a/packages/SystemUI/res/values-television/config.xml +++ b/packages/SystemUI/res/values-television/config.xml @@ -33,6 +33,7 @@ <item>com.android.systemui.statusbar.tv.notifications.TvNotificationPanel</item> <item>com.android.systemui.statusbar.tv.notifications.TvNotificationHandler</item> <item>com.android.systemui.statusbar.tv.VpnStatusObserver</item> + <item>com.android.systemui.globalactions.GlobalActionsComponent</item> <item>com.android.systemui.usb.StorageNotification</item> <item>com.android.systemui.power.PowerUI</item> <item>com.android.systemui.media.RingtonePlayer</item> diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 067d56f3d157..d2ed6017b205 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -143,6 +143,7 @@ <attr name="handleThickness" format="dimension" /> <attr name="handleColor" format="color" /> <attr name="scrimColor" format="color" /> + <attr name="containerBackgroundColor" format="color" /> <attr name="isVertical" format="boolean" /> @@ -178,6 +179,7 @@ <attr name="handleThickness" /> <attr name="handleColor" /> <attr name="scrimColor" /> + <attr name="containerBackgroundColor" /> </declare-styleable> <declare-styleable name="MagnifierView"> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index b4deaa0af543..82ce881b2fbc 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -396,10 +396,6 @@ <!-- Whether or not the notifications should always fade as they are dismissed. --> <bool name="config_fadeNotificationsOnDismiss">false</bool> - <!-- Whether or not the parent of the notification row itself is being translated when swiped or - its children views. If true, then the contents are translated and vice versa. --> - <bool name="config_translateNotificationContentsOnSwipe">true</bool> - <!-- Whether or not the fade on the notification is based on the amount that it has been swiped off-screen. --> <bool name="config_fadeDependingOnAmountSwiped">false</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 0ccde60f9bfd..9b860c75f476 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -347,7 +347,7 @@ <!-- Padding to make tappable chip height 48dp (18+11+11+4+4) --> <dimen name="screenshot_action_chip_margin_vertical">4dp</dimen> <dimen name="screenshot_action_chip_padding_vertical">11dp</dimen> - <dimen name="screenshot_action_chip_icon_size">18dp</dimen> + <dimen name="screenshot_action_chip_icon_size">18sp</dimen> <!-- Padding on each side of the icon for icon-only chips --> <dimen name="screenshot_action_chip_icon_only_padding_horizontal">14dp</dimen> <!-- Padding at the edges of the chip for icon-and-text chips --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 91301dfda5e8..93ce8f3c2a96 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2979,6 +2979,8 @@ <!-- Content description for a chip in the status bar showing that the user is currently on a phone call. [CHAR LIMIT=NONE] --> <string name="ongoing_phone_call_content_description">Ongoing phone call</string> + <!-- Placeholder for string describing changes in global actions --> + <string name="global_actions_change_description" translatable="false"><xliff:g>%1$s</xliff:g></string> <!-- URL for more information about changes in global actions --> <string name="global_actions_change_url" translatable="false"></string> </resources> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index f3a6d6377c6d..38f8f7ac321f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -249,6 +249,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public void onStateChanged(int newState) { mStatusBarState = newState; } + + @Override + public void onExpandedChanged(boolean isExpanded) { + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onShadeExpandedChanged(isExpanded); + } + } + } }; HashMap<Integer, SimData> mSimDatas = new HashMap<>(); @@ -775,6 +785,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) { mFingerprintLockedOut = true; + if (isUdfpsEnrolled()) { + updateFingerprintListeningState(); + } } for (int i = 0; i < mCallbacks.size(); i++) { @@ -2115,7 +2128,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab || (!getUserCanSkipBouncer(getCurrentUser()) && !isEncryptedOrLockdown(getCurrentUser()) && !userNeedsStrongAuth() - && userDoesNotHaveTrust); + && userDoesNotHaveTrust + && !mFingerprintLockedOut); return shouldListenKeyguardState && shouldListenUserState && shouldListenBouncerState && shouldListenUdfpsState; } @@ -3244,6 +3258,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); pw.println(" trustManaged=" + getUserTrustIsManaged(userId)); pw.println(" udfpsEnrolled=" + isUdfpsEnrolled()); + pw.println(" mFingerprintLockedOut=" + mFingerprintLockedOut); pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId)); if (isUdfpsEnrolled()) { pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true)); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index e561a5a84f24..9849a7efe837 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -333,4 +333,8 @@ public class KeyguardUpdateMonitorCallback { */ public void onRequireUnlockForNfc() { } + /** + * Called when the notification shade is expanded or collapsed. + */ + public void onShadeExpandedChanged(boolean expanded) { } } diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 64a683e78953..a68f79604b25 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -130,10 +130,7 @@ public class ImageWallpaper extends WallpaperService { .getBounds(); mHeight = window.height(); mWidth = window.width(); - mMiniBitmap = null; - if (mWorker != null && mWorker.getThreadHandler() != null) { - mWorker.getThreadHandler().post(this::updateMiniBitmap); - } + mRenderer.setOnBitmapChanged(this::updateMiniBitmap); } EglHelper getEglHelperInstance() { @@ -177,20 +174,19 @@ public class ImageWallpaper extends WallpaperService { mPageOffset = (1 - imgWidth) / (float) (mPages - 1); } - private void updateMiniBitmap() { - mRenderer.useBitmap(b -> { - int size = Math.min(b.getWidth(), b.getHeight()); - float scale = 1.0f; - if (size > MIN_SURFACE_WIDTH) { - scale = (float) MIN_SURFACE_WIDTH / (float) size; - } - mImgHeight = b.getHeight(); - mImgWidth = b.getWidth(); - mMiniBitmap = Bitmap.createScaledBitmap(b, (int) Math.max(scale * b.getWidth(), 1), - (int) Math.max(scale * b.getHeight(), 1), false); - computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap); - mLocalColorsToAdd.clear(); - }); + private void updateMiniBitmap(Bitmap b) { + if (b == null) return; + int size = Math.min(b.getWidth(), b.getHeight()); + float scale = 1.0f; + if (size > MIN_SURFACE_WIDTH) { + scale = (float) MIN_SURFACE_WIDTH / (float) size; + } + mImgHeight = b.getHeight(); + mImgWidth = b.getWidth(); + mMiniBitmap = Bitmap.createScaledBitmap(b, (int) Math.max(scale * b.getWidth(), 1), + (int) Math.max(scale * b.getHeight(), 1), false); + computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap); + mLocalColorsToAdd.clear(); } private void updateSurfaceSize() { diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index b0f4da251208..affad7a57d86 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -391,9 +391,9 @@ public class SwipeHelper implements Gefingerpoken { boolean animateLeft = (Math.abs(velocity) > getEscapeVelocity() && velocity < 0) || (getTranslation(animView) < 0 && !isDismissAll); if (animateLeft || animateLeftForRtl || animateUpForMenu) { - newPos = -getSize(animView); + newPos = -getTotalTranslationLength(animView); } else { - newPos = getSize(animView); + newPos = getTotalTranslationLength(animView); } long duration; if (fixedDuration == 0) { @@ -470,6 +470,15 @@ public class SwipeHelper implements Gefingerpoken { } /** + * Get the total translation length where we want to swipe to when dismissing the view. By + * default this is the size of the view, but can also be larger. + * @param animView the view to ask about + */ + protected float getTotalTranslationLength(View animView) { + return getSize(animView); + } + + /** * Called to update the dismiss animation. */ protected void prepareDismissAnimation(View view, Animator anim) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java index 47f373920b90..05256e646948 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java @@ -53,7 +53,7 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu { @FloatRange(from = 0.0, to = 1.0) private static final float DEFAULT_POSITION_X_PERCENT = 1.0f; @FloatRange(from = 0.0, to = 1.0) - private static final float DEFAULT_POSITION_Y_PERCENT = 0.8f; + private static final float DEFAULT_POSITION_Y_PERCENT = 0.9f; private final Context mContext; private final AccessibilityFloatingMenuView mMenuView; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java index 63cfd5123c96..ee09c620ec1d 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java @@ -143,7 +143,7 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { } void updateItemPadding(int padding, int size) { - itemView.setPaddingRelative(padding, padding, padding, padding); + itemView.setPaddingRelative(padding, padding, padding, 0); } } @@ -154,7 +154,7 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { @Override void updateItemPadding(int padding, int size) { - final int paddingBottom = size <= 2 ? padding : 0; + final int paddingBottom = size <= 1 ? padding : 0; itemView.setPaddingRelative(padding, padding, padding, paddingBottom); } } @@ -166,7 +166,7 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { @Override void updateItemPadding(int padding, int size) { - itemView.setPaddingRelative(padding, 0, padding, padding); + itemView.setPaddingRelative(padding, padding, padding, padding); } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index cf577a37d625..77cca2e3089c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -24,9 +24,13 @@ import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.settingslib.Utils +import com.android.systemui.statusbar.CircleReveal +import com.android.systemui.statusbar.LiftReveal +import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope @@ -49,10 +53,12 @@ class AuthRippleController @Inject constructor( private val commandRegistry: CommandRegistry, private val notificationShadeWindowController: NotificationShadeWindowController, private val bypassController: KeyguardBypassController, + private val biometricUnlockController: BiometricUnlockController, rippleView: AuthRippleView? ) : ViewController<AuthRippleView>(rippleView) { var fingerprintSensorLocation: PointF? = null private var faceSensorLocation: PointF? = null + private var circleReveal: LightRevealEffect? = null @VisibleForTesting public override fun onViewAttached() { @@ -96,15 +102,47 @@ class AuthRippleController @Inject constructor( private fun showRipple() { notificationShadeWindowController.setForcePluginOpen(true, this) - mView.startRipple(Runnable { - notificationShadeWindowController.setForcePluginOpen(false, this) - }) + val biometricUnlockMode = biometricUnlockController.mode + val useCircleReveal = circleReveal != null && + (biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK || + biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING || + biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM) + val lightRevealScrim = statusBar.lightRevealScrim + if (useCircleReveal) { + lightRevealScrim?.revealEffect = circleReveal!! + } + + mView.startRipple( + /* end runnable */ + Runnable { + notificationShadeWindowController.setForcePluginOpen(false, this) + if (useCircleReveal) { + lightRevealScrim?.revealEffect = LiftReveal + } + }, + /* circleReveal */ + if (useCircleReveal) { + lightRevealScrim + } else { + null + } + ) } fun updateSensorLocation() { fingerprintSensorLocation = authController.fingerprintSensorLocation faceSensorLocation = authController.faceAuthSensorLocation - statusBar.updateCircleReveal() + fingerprintSensorLocation?.let { + circleReveal = CircleReveal( + it.x, + it.y, + 0f, + Math.max( + Math.max(it.x, statusBar.displayWidth - it.x), + Math.max(it.y, statusBar.displayHeight - it.y) + ) + ) + } } private fun updateRippleColor() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 75373abc5124..dd73c4f8d071 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -31,6 +31,7 @@ import android.util.MathUtils import android.view.View import android.view.animation.PathInterpolator import com.android.internal.graphics.ColorUtils +import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.charging.RippleShader private const val RIPPLE_ANIMATION_DURATION: Long = 1533 @@ -70,51 +71,79 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at .toFloat() } - fun startRipple(onAnimationEnd: Runnable?) { + fun startRipple(onAnimationEnd: Runnable?, lightReveal: LightRevealScrim?) { if (rippleInProgress) { return // Ignore if ripple effect is already playing } - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.interpolator = PathInterpolator(0.4f, 0f, 0f, 1f) - animator.duration = RIPPLE_ANIMATION_DURATION - animator.addUpdateListener { animator -> - val now = animator.currentPlayTime - rippleShader.progress = animator.animatedValue as Float - rippleShader.time = now.toFloat() - rippleShader.distortionStrength = 1 - rippleShader.progress - invalidate() + val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply { + interpolator = PathInterpolator(0.4f, 0f, 0f, 1f) + duration = RIPPLE_ANIMATION_DURATION + addUpdateListener { animator -> + val now = animator.currentPlayTime + rippleShader.progress = animator.animatedValue as Float + rippleShader.time = now.toFloat() + + lightReveal?.revealAmount = animator.animatedValue as Float + invalidate() + } } - val alphaInAnimator = ValueAnimator.ofInt(0, 127) - alphaInAnimator.duration = 167 - alphaInAnimator.addUpdateListener { alphaInAnimator -> - rippleShader.color = ColorUtils.setAlphaComponent(rippleShader.color, - alphaInAnimator.animatedValue as Int) - invalidate() + + val revealAnimator = ValueAnimator.ofFloat(0f, 1f).apply { + interpolator = rippleAnimator.interpolator + startDelay = 10 + duration = rippleAnimator.duration + addUpdateListener { animator -> + lightReveal?.revealAmount = animator.animatedValue as Float + } } - val alphaOutAnimator = ValueAnimator.ofInt(127, 0) - alphaOutAnimator.startDelay = 417 - alphaOutAnimator.duration = 1116 - alphaOutAnimator.addUpdateListener { alphaOutAnimator -> - rippleShader.color = ColorUtils.setAlphaComponent(rippleShader.color, - alphaOutAnimator.animatedValue as Int) - invalidate() + + val alphaInAnimator = ValueAnimator.ofInt(0, 127).apply { + duration = 167 + addUpdateListener { animator -> + rippleShader.color = ColorUtils.setAlphaComponent( + rippleShader.color, + animator.animatedValue as Int + ) + invalidate() + } } - val animatorSet = AnimatorSet() - animatorSet.playTogether(animator, alphaInAnimator, alphaOutAnimator) - animatorSet.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { - onAnimationEnd?.run() - rippleInProgress = false - visibility = GONE + val alphaOutAnimator = ValueAnimator.ofInt(127, 0).apply { + startDelay = 417 + duration = 1116 + addUpdateListener { animator -> + rippleShader.color = ColorUtils.setAlphaComponent( + rippleShader.color, + animator.animatedValue as Int + ) + invalidate() } - }) + } + + val animatorSet = AnimatorSet().apply { + playTogether( + rippleAnimator, + revealAnimator, + alphaInAnimator, + alphaOutAnimator + ) + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?) { + rippleInProgress = true + visibility = VISIBLE + } + + override fun onAnimationEnd(animation: Animator?) { + onAnimationEnd?.run() + rippleInProgress = false + visibility = GONE + } + }) + } // TODO (b/185124905): custom haptic TBD // vibrate() animatorSet.start() - visibility = VISIBLE - rippleInProgress = true } fun setColor(color: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index ff95604088ed..7b34e52c16e8 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -38,6 +38,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; +import com.android.systemui.doze.DozeMachine.State; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.Assert; @@ -96,6 +97,7 @@ public class DozeTriggers implements DozeMachine.Part { private long mNotificationPulseTime; private boolean mPulsePending; + private Runnable mAodInterruptRunnable; /** see {@link #onProximityFar} prox for callback */ private boolean mWantProxSensor; @@ -303,11 +305,16 @@ public class DozeTriggers implements DozeMachine.Part { } else if (isPickup) { gentleWakeUp(pulseReason); } else if (isUdfpsLongPress) { + final State state = mMachine.getState(); + if (state == State.DOZE_AOD || state == State.DOZE) { + // Since the gesture won't be received by the UDFPS view, we need to + // manually inject an event once the display is ON + mAodInterruptRunnable = () -> + mAuthController.onAodInterrupt((int) screenX, (int) screenY, + rawValues[3] /* major */, rawValues[4] /* minor */); + } + requestPulse(DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS, true, null); - // Since the gesture won't be received by the UDFPS view, manually inject an - // event. - mAuthController.onAodInterrupt((int) screenX, (int) screenY, - rawValues[3] /* major */, rawValues[4] /* minor */); } else { mDozeHost.extendPulse(pulseReason); } @@ -439,6 +446,7 @@ public class DozeTriggers implements DozeMachine.Part { public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { switch (newState) { case INITIALIZED: + mAodInterruptRunnable = null; sWakeDisplaySensorState = true; mBroadcastReceiver.register(mBroadcastDispatcher); mDozeHost.addCallback(mHostCallback); @@ -448,6 +456,7 @@ public class DozeTriggers implements DozeMachine.Part { break; case DOZE: case DOZE_AOD: + mAodInterruptRunnable = null; mWantProxSensor = newState != DozeMachine.State.DOZE; mWantSensors = true; mWantTouchScreenSensors = true; @@ -494,6 +503,11 @@ public class DozeTriggers implements DozeMachine.Part { || state == Display.STATE_DOZE_SUSPEND || state == Display.STATE_OFF; mDozeSensors.setProxListening(mWantProxSensor && lowPowerStateOrOff); mDozeSensors.setListening(mWantSensors, mWantTouchScreenSensors, lowPowerStateOrOff); + + if (mAodInterruptRunnable != null && state == Display.STATE_ON) { + mAodInterruptRunnable.run(); + mAodInterruptRunnable = null; + } } /** @@ -576,6 +590,8 @@ public class DozeTriggers implements DozeMachine.Part { @Override public void dump(PrintWriter pw) { + pw.println(" mAodInterruptRunnable=" + mAodInterruptRunnable); + pw.print(" notificationPulseTime="); pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime)); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index ff3cb2102d60..fbe06b02d955 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -21,6 +21,7 @@ import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED; import android.app.AlarmManager; import android.content.Context; +import android.content.res.Configuration; import android.os.Handler; import android.os.SystemClock; import android.provider.Settings; @@ -34,6 +35,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.AlarmTimeout; import com.android.systemui.util.wakelock.WakeLock; @@ -48,7 +50,8 @@ import dagger.Lazy; * The policy controlling doze. */ @DozeScope -public class DozeUi implements DozeMachine.Part, TunerService.Tunable { +public class DozeUi implements DozeMachine.Part, TunerService.Tunable, + ConfigurationController.ConfigurationListener { // if enabled, calls dozeTimeTick() whenever the time changes: private static final boolean BURN_IN_TESTING_ENABLED = false; private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min @@ -63,6 +66,7 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable { private final DozeLog mDozeLog; private final Lazy<StatusBarStateController> mStatusBarStateController; private final TunerService mTunerService; + private final ConfigurationController mConfigurationController; private boolean mKeyguardShowing; private final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback = @@ -84,6 +88,11 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable { mHandler.post(mWakeLock.wrap(() -> {})); } } + + @Override + public void onShadeExpandedChanged(boolean expanded) { + updateAnimateScreenOff(); + } }; private long mLastTimeTickElapsed = 0; @@ -93,7 +102,8 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable { WakeLock wakeLock, DozeHost host, @Main Handler handler, DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor, DozeLog dozeLog, TunerService tunerService, - Lazy<StatusBarStateController> statusBarStateController) { + Lazy<StatusBarStateController> statusBarStateController, + ConfigurationController configurationController) { mContext = context; mWakeLock = wakeLock; mHost = host; @@ -107,11 +117,15 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable { mStatusBarStateController = statusBarStateController; mTunerService.addTunable(this, Settings.Secure.DOZE_ALWAYS_ON); + + mConfigurationController = configurationController; + mConfigurationController.addCallback(this); } @Override public void destroy() { mTunerService.removeTunable(this); + mConfigurationController.removeCallback(this); } @Override @@ -274,4 +288,9 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable { updateAnimateScreenOff(); } } + + @Override + public void onConfigChanged(Configuration newConfig) { + updateAnimateScreenOff(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index dfd85fe4dc90..bb44b09f1bce 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -30,6 +30,7 @@ import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; import android.content.Context; import android.content.DialogInterface; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.drawable.Drawable; @@ -72,6 +73,7 @@ import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -112,47 +114,101 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite @VisibleForTesting boolean mShowLockScreenCards = false; + private final KeyguardStateController.Callback mKeyguardStateControllerListener = + new KeyguardStateController.Callback() { + @Override + public void onUnlockedChanged() { + if (mDialog != null) { + ActionsDialog dialog = (ActionsDialog) mDialog; + boolean unlocked = mKeyguardStateController.isUnlocked(); + if (dialog.mWalletViewController != null) { + dialog.mWalletViewController.onDeviceLockStateChanged(!unlocked); + } + + if (unlocked) { + dialog.hideLockMessage(); + } + } + } + }; + + private final ContentObserver mSettingsObserver = new ContentObserver(mMainHandler) { + @Override + public void onChange(boolean selfChange) { + onPowerMenuLockScreenSettingsChanged(); + } + }; + /** * @param context everything needs a context :( */ @Inject - public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs, - AudioManager audioManager, IDreamManager iDreamManager, - DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils, + public GlobalActionsDialog( + Context context, + GlobalActionsManager windowManagerFuncs, + AudioManager audioManager, + IDreamManager iDreamManager, + DevicePolicyManager devicePolicyManager, + LockPatternUtils lockPatternUtils, BroadcastDispatcher broadcastDispatcher, TelephonyListenerManager telephonyListenerManager, - GlobalSettings globalSettings, SecureSettings secureSettings, - @Nullable Vibrator vibrator, @Main Resources resources, - ConfigurationController configurationController, ActivityStarter activityStarter, - KeyguardStateController keyguardStateController, UserManager userManager, - TrustManager trustManager, IActivityManager iActivityManager, - @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger, - NotificationShadeDepthController depthController, SysuiColorExtractor colorExtractor, + GlobalSettings globalSettings, + SecureSettings secureSettings, + @Nullable Vibrator vibrator, + @Main Resources resources, + ConfigurationController configurationController, + ActivityStarter activityStarter, + KeyguardStateController keyguardStateController, + UserManager userManager, + TrustManager trustManager, + IActivityManager iActivityManager, + @Nullable TelecomManager telecomManager, + MetricsLogger metricsLogger, + NotificationShadeDepthController depthController, + SysuiColorExtractor colorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, IWindowManager iWindowManager, @Background Executor backgroundExecutor, UiEventLogger uiEventLogger, - RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) { - - super(context, windowManagerFuncs, - audioManager, iDreamManager, - devicePolicyManager, lockPatternUtils, - broadcastDispatcher, telephonyListenerManager, - globalSettings, secureSettings, - vibrator, resources, + RingerModeTracker ringerModeTracker, + SysUiState sysUiState, + @Main Handler handler, + PackageManager packageManager, + StatusBar statusBar) { + + super(context, + windowManagerFuncs, + audioManager, + iDreamManager, + devicePolicyManager, + lockPatternUtils, + broadcastDispatcher, + telephonyListenerManager, + globalSettings, + secureSettings, + vibrator, + resources, configurationController, - keyguardStateController, userManager, - trustManager, iActivityManager, - telecomManager, metricsLogger, - depthController, colorExtractor, + keyguardStateController, + userManager, + trustManager, + iActivityManager, + telecomManager, + metricsLogger, + depthController, + colorExtractor, statusBarService, notificationShadeWindowController, iWindowManager, backgroundExecutor, uiEventLogger, null, - ringerModeTracker, sysUiState, handler); + ringerModeTracker, + sysUiState, + handler, + packageManager, + statusBar); mLockPatternUtils = lockPatternUtils; mKeyguardStateController = keyguardStateController; @@ -162,34 +218,22 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite mNotificationShadeWindowController = notificationShadeWindowController; mSysUiState = sysUiState; mActivityStarter = activityStarter; - keyguardStateController.addCallback(new KeyguardStateController.Callback() { - @Override - public void onUnlockedChanged() { - if (mDialog != null) { - ActionsDialog dialog = (ActionsDialog) mDialog; - boolean unlocked = mKeyguardStateController.isUnlocked(); - if (dialog.mWalletViewController != null) { - dialog.mWalletViewController.onDeviceLockStateChanged(!unlocked); - } - if (unlocked) { - dialog.hideLockMessage(); - } - } - } - }); + mKeyguardStateController.addCallback(mKeyguardStateControllerListener); // Listen for changes to show pay on the power menu while locked onPowerMenuLockScreenSettingsChanged(); mGlobalSettings.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), false /* notifyForDescendants */, - new ContentObserver(handler) { - @Override - public void onChange(boolean selfChange) { - onPowerMenuLockScreenSettingsChanged(); - } - }); + mSettingsObserver); + } + + @Override + public void destroy() { + super.destroy(); + mKeyguardStateController.removeCallback(mKeyguardStateControllerListener); + mGlobalSettings.unregisterContentObserver(mSettingsObserver); } /** @@ -227,7 +271,8 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite ActionsDialog dialog = new ActionsDialog(getContext(), mAdapter, mOverflowAdapter, this::getWalletViewController, mDepthController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, - mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger()); + mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger(), + getStatusBar()); if (shouldShowLockMessage(dialog)) { dialog.showLockMessage(); @@ -295,11 +340,13 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing, - MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger) { + MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger, + StatusBar statusBar) { super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions, adapter, overflowAdapter, depthController, sysuiColorExtractor, statusBarService, notificationShadeWindowController, sysuiState, - onRotateCallback, keyguardShowing, powerAdapter, uiEventLogger, null); + onRotateCallback, keyguardShowing, powerAdapter, uiEventLogger, null, + statusBar); mWalletFactory = walletFactory; // Update window attributes diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 8e152830e208..42cd4f7da3a8 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -74,8 +74,10 @@ import android.telephony.TelephonyManager; import android.util.ArraySet; import android.util.Log; import android.view.ContextThemeWrapper; +import android.view.GestureDetector; import android.view.IWindowManager; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -119,6 +121,7 @@ import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.scrim.ScrimDrawable; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -174,6 +177,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final IDreamManager mDreamManager; private final DevicePolicyManager mDevicePolicyManager; private final LockPatternUtils mLockPatternUtils; + private final TelephonyListenerManager mTelephonyListenerManager; private final KeyguardStateController mKeyguardStateController; private final BroadcastDispatcher mBroadcastDispatcher; protected final GlobalSettings mGlobalSettings; @@ -228,6 +232,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms protected Handler mMainHandler; private int mSmallestScreenWidthDp; + private final StatusBar mStatusBar; @VisibleForTesting public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum { @@ -304,31 +309,46 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene * @param context everything needs a context :( */ @Inject - public GlobalActionsDialogLite(Context context, GlobalActionsManager windowManagerFuncs, - AudioManager audioManager, IDreamManager iDreamManager, - DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils, + public GlobalActionsDialogLite( + Context context, + GlobalActionsManager windowManagerFuncs, + AudioManager audioManager, + IDreamManager iDreamManager, + DevicePolicyManager devicePolicyManager, + LockPatternUtils lockPatternUtils, BroadcastDispatcher broadcastDispatcher, TelephonyListenerManager telephonyListenerManager, - GlobalSettings globalSettings, SecureSettings secureSettings, - @Nullable Vibrator vibrator, @Main Resources resources, + GlobalSettings globalSettings, + SecureSettings secureSettings, + @Nullable Vibrator vibrator, + @Main Resources resources, ConfigurationController configurationController, - KeyguardStateController keyguardStateController, UserManager userManager, - TrustManager trustManager, IActivityManager iActivityManager, - @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger, - NotificationShadeDepthController depthController, SysuiColorExtractor colorExtractor, + KeyguardStateController keyguardStateController, + UserManager userManager, + TrustManager trustManager, + IActivityManager iActivityManager, + @Nullable TelecomManager telecomManager, + MetricsLogger metricsLogger, + NotificationShadeDepthController depthController, + SysuiColorExtractor colorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, IWindowManager iWindowManager, @Background Executor backgroundExecutor, UiEventLogger uiEventLogger, GlobalActionsInfoProvider infoProvider, - RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) { + RingerModeTracker ringerModeTracker, + SysUiState sysUiState, + @Main Handler handler, + PackageManager packageManager, + StatusBar statusBar) { mContext = context; mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; mDreamManager = iDreamManager; mDevicePolicyManager = devicePolicyManager; mLockPatternUtils = lockPatternUtils; + mTelephonyListenerManager = telephonyListenerManager; mKeyguardStateController = keyguardStateController; mBroadcastDispatcher = broadcastDispatcher; mGlobalSettings = globalSettings; @@ -351,7 +371,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mRingerModeTracker = ringerModeTracker; mSysUiState = sysUiState; mMainHandler = handler; - mSmallestScreenWidthDp = mContext.getResources().getConfiguration().smallestScreenWidthDp; + mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp; + mStatusBar = statusBar; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -360,11 +381,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter); - mHasTelephony = - context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); + mHasTelephony = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); // get notified of phone state changes - telephonyListenerManager.addServiceStateListener(mPhoneStateListener); + mTelephonyListenerManager.addServiceStateListener(mPhoneStateListener); mGlobalSettings.registerContentObserver( Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, mAirplaneModeObserver); @@ -384,6 +404,16 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mConfigurationController.addCallback(this); } + /** + * Clean up callbacks + */ + public void destroy() { + mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); + mTelephonyListenerManager.removeServiceStateListener(mPhoneStateListener); + mGlobalSettings.unregisterContentObserver(mAirplaneModeObserver); + mConfigurationController.removeCallback(this); + } + protected Context getContext() { return mContext; } @@ -392,6 +422,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene return mUiEventLogger; } + protected StatusBar getStatusBar() { + return mStatusBar; + } + /** * Show the global actions dialog (creating if necessary) * @@ -625,7 +659,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mDepthController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger, - mInfoProvider); + mInfoProvider, mStatusBar); dialog.setOnDismissListener(this); dialog.setOnShowListener(this); @@ -679,14 +713,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mDialog.refreshDialog(); } } - - /** - * Clean up callbacks - */ - public void destroy() { - mConfigurationController.removeCallback(this); - } - /** * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is * called when the quick access wallet requests dismissal. @@ -2008,7 +2034,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } }; - private ContentObserver mAirplaneModeObserver = new ContentObserver(mMainHandler) { + private final ContentObserver mAirplaneModeObserver = new ContentObserver(mMainHandler) { @Override public void onChange(boolean selfChange) { onAirplaneModeChanged(); @@ -2100,9 +2126,53 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected final Runnable mOnRotateCallback; private UiEventLogger mUiEventLogger; private GlobalActionsInfoProvider mInfoProvider; + private GestureDetector mGestureDetector; + private StatusBar mStatusBar; protected ViewGroup mContainer; + @VisibleForTesting + protected GestureDetector.SimpleOnGestureListener mGestureListener = + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDown(MotionEvent e) { + // All gestures begin with this message, so continue listening + return true; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + // Close without opening shade + mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); + cancel(); + return false; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, + float distanceY) { + if (distanceY < 0 && distanceY > distanceX + && e1.getY() <= mStatusBar.getStatusBarHeight()) { + // Downwards scroll from top + openShadeAndDismiss(); + return true; + } + return false; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + if (velocityY > 0 && Math.abs(velocityY) > Math.abs(velocityX) + && e1.getY() <= mStatusBar.getStatusBarHeight()) { + // Downwards fling from top + openShadeAndDismiss(); + return true; + } + return false; + } + }; + ActionsDialogLite(Context context, int themeRes, MyAdapter adapter, MyOverflowAdapter overflowAdapter, NotificationShadeDepthController depthController, @@ -2110,7 +2180,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene NotificationShadeWindowController notificationShadeWindowController, SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing, MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger, - @Nullable GlobalActionsInfoProvider infoProvider) { + @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar) { super(context, themeRes); mContext = context; mAdapter = adapter; @@ -2125,6 +2195,9 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mKeyguardShowing = keyguardShowing; mUiEventLogger = uiEventLogger; mInfoProvider = infoProvider; + mStatusBar = statusBar; + + mGestureDetector = new GestureDetector(mContext, mGestureListener); // Window initialization Window window = getWindow(); @@ -2146,6 +2219,23 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene initializeLayout(); } + @Override + public boolean onTouchEvent(MotionEvent event) { + return mGestureDetector.onTouchEvent(event) || super.onTouchEvent(event); + } + + private void openShadeAndDismiss() { + mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); + if (mStatusBar.isKeyguardShowing()) { + // match existing lockscreen behavior to open QS when swiping from status bar + mStatusBar.animateExpandSettingsPanel(null); + } else { + // otherwise, swiping down should expand notification shade + mStatusBar.animateExpandNotificationsPanel(); + } + dismiss(); + } + private ListPopupWindow createPowerOverflowPopup() { GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu( new ContextThemeWrapper( @@ -2194,9 +2284,9 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mGlobalActionsLayout.setRotationListener(this::onRotate); mGlobalActionsLayout.setAdapter(mAdapter); mContainer = findViewById(com.android.systemui.R.id.global_actions_container); - mContainer.setOnClickListener(v -> { - mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); - cancel(); + mContainer.setOnTouchListener((v, event) -> { + mGestureDetector.onTouchEvent(event); + return v.onTouchEvent(event); }); View overflowButton = findViewById( diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index 178a74cecc2e..e37d3d586ccc 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -32,7 +32,6 @@ import android.widget.TextView; import com.android.internal.R; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.Utils; -import com.android.systemui.Dependency; import com.android.systemui.plugins.GlobalActions; import com.android.systemui.scrim.ScrimDrawable; import com.android.systemui.statusbar.BlurUtils; @@ -52,19 +51,24 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks private final KeyguardStateController mKeyguardStateController; private final DeviceProvisionedController mDeviceProvisionedController; private final BlurUtils mBlurUtils; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final CommandQueue mCommandQueue; private GlobalActionsDialogLite mGlobalActionsDialog; private boolean mDisabled; @Inject public GlobalActionsImpl(Context context, CommandQueue commandQueue, - Lazy<GlobalActionsDialogLite> globalActionsDialogLazy, BlurUtils blurUtils) { + Lazy<GlobalActionsDialogLite> globalActionsDialogLazy, BlurUtils blurUtils, + KeyguardStateController keyguardStateController, + DeviceProvisionedController deviceProvisionedController, + KeyguardUpdateMonitor keyguardUpdateMonitor) { mContext = context; mGlobalActionsDialogLazy = globalActionsDialogLazy; - mKeyguardStateController = Dependency.get(KeyguardStateController.class); - mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); + mKeyguardStateController = keyguardStateController; + mDeviceProvisionedController = deviceProvisionedController; mCommandQueue = commandQueue; mBlurUtils = blurUtils; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; mCommandQueue.addCallback(this); } @@ -83,7 +87,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks mGlobalActionsDialog = mGlobalActionsDialogLazy.get(); mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(), mDeviceProvisionedController.isDeviceProvisioned()); - Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth(); + mKeyguardUpdateMonitor.requestFaceAuth(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt index 39008eecd6a2..17b532a643cd 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt @@ -25,6 +25,7 @@ import android.util.Log import android.view.LayoutInflater import android.view.ViewGroup import android.widget.ImageView +import android.widget.TextView import com.android.systemui.R import com.android.systemui.controls.controller.ControlsController import com.android.systemui.plugins.ActivityStarter @@ -70,6 +71,11 @@ class GlobalActionsInfoProvider @Inject constructor( val view = LayoutInflater.from(context).inflate(R.layout.global_actions_change_panel, parent, false) + + val walletTitle = walletClient.serviceLabel ?: context.getString(R.string.wallet_title) + val message = view.findViewById<TextView>(R.id.global_actions_change_message) + message?.setText(context.getString(R.string.global_actions_change_description, walletTitle)) + val button = view.findViewById<ImageView>(R.id.global_actions_change_button) button.setOnClickListener { _ -> dismissParent.run() diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java index 01a353ce8f1f..d30783c29f92 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java @@ -46,6 +46,7 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer { private final ImageGLWallpaper mWallpaper; private final Rect mSurfaceSize = new Rect(); private final WallpaperTexture mTexture; + private Consumer<Bitmap> mOnBitmapUpdated; public ImageWallpaperRenderer(Context context) { final WallpaperManager wpm = context.getSystemService(WallpaperManager.class); @@ -60,10 +61,9 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer { /** * @hide - * @return */ - public void useBitmap(Consumer<Bitmap> c) { - mTexture.use(c); + public void setOnBitmapChanged(Consumer<Bitmap> c) { + mOnBitmapUpdated = c; } @Override @@ -80,6 +80,8 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer { mTexture.use(bitmap -> { if (bitmap == null) { Log.w(TAG, "reload texture failed!"); + } else if (mOnBitmapUpdated != null) { + mOnBitmapUpdated.accept(bitmap); } mWallpaper.setup(bitmap); }); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java index bec4ce6ba658..fc5f3b8ae994 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java @@ -20,7 +20,6 @@ import android.annotation.Nullable; import android.content.res.ColorStateList; import android.graphics.Color; import android.text.TextUtils; -import android.view.View; import androidx.annotation.IntDef; @@ -202,10 +201,7 @@ public class KeyguardIndicationRotateTextViewController extends mCurrIndicationType = type; mIndicationQueue.removeIf(x -> x == type); - if (mCurrIndicationType == INDICATION_TYPE_NONE) { - mView.setVisibility(View.GONE); - } else { - mView.setVisibility(View.VISIBLE); + if (mCurrIndicationType != INDICATION_TYPE_NONE) { mIndicationQueue.add(type); // re-add to show later } @@ -299,7 +295,7 @@ public class KeyguardIndicationRotateTextViewController extends } } - private static final int INDICATION_TYPE_NONE = -1; + static final int INDICATION_TYPE_NONE = -1; public static final int INDICATION_TYPE_OWNER_INFO = 0; public static final int INDICATION_TYPE_DISCLOSURE = 1; public static final int INDICATION_TYPE_LOGOUT = 2; diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt index 77d789292e5e..2bf102f724f4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt @@ -186,11 +186,9 @@ class KeyguardMediaController @Inject constructor( } private fun hideMediaPlayer() { - if (useSplitShade) { - setVisibility(splitShadeContainer, View.GONE) - } else { - setVisibility(singlePaneContainer, View.GONE) - } + // always hide splitShadeContainer as it's initially visible and may influence layout + setVisibility(splitShadeContainer, View.GONE) + setVisibility(singlePaneContainer, View.GONE) } private fun setVisibility(view: ViewGroup?, newVisibility: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index c2b580773424..19190cd4a17d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -46,6 +46,7 @@ import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.constraintlayout.widget.ConstraintSet; +import com.android.internal.jank.InteractionJankMonitor; import com.android.settingslib.widget.AdaptiveIcon; import com.android.systemui.R; import com.android.systemui.animation.ActivityLaunchAnimator; @@ -468,7 +469,8 @@ public class MediaControlPanel { TransitionLayout player) { // TODO(b/174236650): Make sure that the carousel indicator also fades out. // TODO(b/174236650): Instrument the animation to measure jank. - return new GhostedViewLaunchAnimatorController(player) { + return new GhostedViewLaunchAnimatorController(player, + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER) { @Override protected float getCurrentTopCornerRadius() { return ((IlluminationDrawable) player.getBackground()).getCornerRadius(); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 5b1e039ad0f8..28d336ea18d5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -39,6 +39,7 @@ import android.media.session.MediaSession import android.net.Uri import android.os.Parcelable import android.os.UserHandle +import android.provider.Settings import android.service.notification.StatusBarNotification import android.text.TextUtils import android.util.Log @@ -54,6 +55,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.notification.row.HybridGroupManager +import com.android.systemui.tuner.TunerService import com.android.systemui.util.Assert import com.android.systemui.util.Utils import com.android.systemui.util.concurrency.DelayableExecutor @@ -114,7 +116,8 @@ class MediaDataManager( private val smartspaceMediaDataProvider: SmartspaceMediaDataProvider, private var useMediaResumption: Boolean, private val useQsMediaPlayer: Boolean, - private val systemClock: SystemClock + private val systemClock: SystemClock, + private val tunerService: TunerService ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener { companion object { @@ -147,6 +150,7 @@ class MediaDataManager( // There should ONLY be at most one Smartspace media recommendation. private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA private var smartspaceSession: SmartspaceSession? = null + private var allowMediaRecommendations = Utils.allowMediaRecommendations(context) @Inject constructor( @@ -164,12 +168,13 @@ class MediaDataManager( mediaDataFilter: MediaDataFilter, activityStarter: ActivityStarter, smartspaceMediaDataProvider: SmartspaceMediaDataProvider, - clock: SystemClock + clock: SystemClock, + tunerService: TunerService ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory, broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener, mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter, activityStarter, smartspaceMediaDataProvider, Utils.useMediaResumption(context), - Utils.useQsMediaPlayer(context), clock) + Utils.useQsMediaPlayer(context), clock, tunerService) private val appChangeReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -243,6 +248,14 @@ class MediaDataManager( }) } smartspaceSession?.let { it.requestSmartspaceUpdate() } + tunerService.addTunable(object : TunerService.Tunable { + override fun onTuningChanged(key: String?, newValue: String?) { + allowMediaRecommendations = Utils.allowMediaRecommendations(context) + if (!allowMediaRecommendations) { + dismissSmartspaceRecommendation(key = smartspaceMediaData.targetId, delay = 0L) + } + } + }, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION) } fun destroy() { @@ -695,8 +708,7 @@ class MediaDataManager( } override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) { - if (!Utils.allowMediaRecommendations(context)) { - Log.d(TAG, "Smartspace recommendation is disabled in Settings.") + if (!allowMediaRecommendations) { return } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 075bc700cfa0..edbf18789e28 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -187,6 +187,12 @@ class MediaHierarchyManager @Inject constructor( private var currentAttachmentLocation = -1 /** + * Is there any active media in the carousel? + */ + private var hasActiveMedia: Boolean = false + get() = mediaHosts.get(LOCATION_QQS)?.visible == true + + /** * Are we currently waiting on an animation to start? */ private var animationPending: Boolean = false @@ -476,8 +482,12 @@ class MediaHierarchyManager @Inject constructor( val viewHost = createUniqueObjectHost() mediaObject.hostView = viewHost mediaObject.addVisibilityChangeListener { + // If QQS changes visibility, we need to force an update to ensure the transition + // goes into the correct state + val stateUpdate = mediaObject.location == LOCATION_QQS + // Never animate because of a visibility change, only state changes should do that - updateDesiredLocation(forceNoAnimation = true) + updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = stateUpdate) } mediaHosts[mediaObject.location] = mediaObject if (mediaObject.location == desiredLocation) { @@ -521,10 +531,15 @@ class MediaHierarchyManager @Inject constructor( * going from the old desired location to the new one. * * @param forceNoAnimation optional parameter telling the system not to animate + * @param forceStateUpdate optional parameter telling the system to update transition state + * even if location did not change */ - private fun updateDesiredLocation(forceNoAnimation: Boolean = false) { + private fun updateDesiredLocation( + forceNoAnimation: Boolean = false, + forceStateUpdate: Boolean = false + ) { val desiredLocation = calculateLocation() - if (desiredLocation != this.desiredLocation) { + if (desiredLocation != this.desiredLocation || forceStateUpdate) { if (this.desiredLocation >= 0) { previousLocation = this.desiredLocation } @@ -784,7 +799,7 @@ class MediaHierarchyManager @Inject constructor( private fun getQSTransformationProgress(): Float { val currentHost = getHost(desiredLocation) val previousHost = getHost(previousLocation) - if (currentHost?.location == LOCATION_QS) { + if (hasActiveMedia && currentHost?.location == LOCATION_QS) { if (previousHost?.location == LOCATION_QQS) { if (previousHost.visible || statusbarState != StatusBarState.KEYGUARD) { return qsExpansion @@ -917,6 +932,7 @@ class MediaHierarchyManager @Inject constructor( val location = when { qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS qsExpansion > 0.4f && onLockscreen -> LOCATION_QS + !hasActiveMedia -> LOCATION_QS onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN else -> LOCATION_QQS diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 0d9749e05262..ff5d0b157c80 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -251,8 +251,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private float mMLResults; // For debugging - private ArrayDeque<String> mPredictionLog = new ArrayDeque<>(); - private ArrayDeque<String> mGestureLog = new ArrayDeque<>(); + private LogArray mPredictionLog = new LogArray(MAX_NUM_LOGGED_PREDICTIONS); + private LogArray mGestureLogInsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES); + private LogArray mGestureLogOutsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES); private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; @@ -631,7 +632,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker return mMLResults >= mMLModelThreshold ? 1 : 0; } - private boolean isWithinTouchRegion(int x, int y) { + private boolean isWithinInsets(int x, int y) { // Disallow if we are in the bottom gesture area if (y >= (mDisplaySize.y - mBottomGestureHeight)) { return false; @@ -644,7 +645,10 @@ public class EdgeBackGestureHandler extends CurrentUserTracker && x < (mDisplaySize.x - 2 * (mEdgeWidthRight + mRightInset))) { return false; } + return true; + } + private boolean isWithinTouchRegion(int x, int y) { // If the point is inside the PiP or Nav bar overlay excluded bounds, then ignore the back // gesture final boolean isInsidePip = mIsInPipMode && mPipExcludedBounds.contains(x, y); @@ -675,14 +679,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker } // For debugging purposes - if (mPredictionLog.size() >= MAX_NUM_LOGGED_PREDICTIONS) { - mPredictionLog.removeFirst(); - } - mPredictionLog.addLast(String.format("Prediction [%d,%d,%d,%d,%f,%d]", + mPredictionLog.log(String.format("Prediction [%d,%d,%d,%d,%f,%d]", System.currentTimeMillis(), x, y, app, mMLResults, withinRange ? 1 : 0)); - if (DEBUG_MISSING_GESTURE) { - Log.d(DEBUG_MISSING_GESTURE_TAG, mPredictionLog.peekLast()); - } // Always allow if the user is in a transient sticky immersive state if (mIsNavBarShownTransiently) { @@ -755,7 +753,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mMLResults = 0; mLogGesture = false; mInRejectedExclusion = false; - mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed + boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY()); + mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed && isWithinInsets && !mGestureBlockingActivityRunning && !QuickStepContract.isBackGestureDisabled(mSysUiFlags) && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); @@ -769,18 +768,13 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mThresholdCrossed = false; } - // For debugging purposes - if (mGestureLog.size() >= MAX_NUM_LOGGED_GESTURES) { - mGestureLog.removeFirst(); - } - mGestureLog.addLast(String.format( + // For debugging purposes, only log edge points + (isWithinInsets ? mGestureLogInsideInsets : mGestureLogOutsideInsets).log(String.format( "Gesture [%d,alw=%B,%B,%B,%B,disp=%s,wl=%d,il=%d,wr=%d,ir=%d,excl=%s]", - System.currentTimeMillis(), mAllowGesture, mIsOnLeftEdge, mIsBackGestureAllowed, + System.currentTimeMillis(), mAllowGesture, mIsOnLeftEdge, + mIsBackGestureAllowed, QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisplaySize, mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion)); - if (DEBUG_MISSING_GESTURE) { - Log.d(DEBUG_MISSING_GESTURE_TAG, mGestureLog.peekLast()); - } } else if (mAllowGesture || mLogGesture) { if (!mThresholdCrossed) { mEndPoint.x = (int) ev.getX(); @@ -907,7 +901,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker pw.println(" mUseMLModel=" + mUseMLModel); pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep); pw.println(" mStartingQuickstepRotation=" + mStartingQuickstepRotation); - pw.println(" mInRejectedExclusion" + mInRejectedExclusion); + pw.println(" mInRejectedExclusion=" + mInRejectedExclusion); pw.println(" mExcludeRegion=" + mExcludeRegion); pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion); pw.println(" mIsInPipMode=" + mIsInPipMode); @@ -922,7 +916,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker pw.println(" mTouchSlop=" + mTouchSlop); pw.println(" mBottomGestureHeight=" + mBottomGestureHeight); pw.println(" mPredictionLog=" + String.join("\n", mPredictionLog)); - pw.println(" mGestureLog=" + String.join("\n", mGestureLog)); + pw.println(" mGestureLogInsideInsets=" + String.join("\n", mGestureLogInsideInsets)); + pw.println(" mGestureLogOutsideInsets=" + String.join("\n", mGestureLogOutsideInsets)); pw.println(" mEdgeBackPlugin=" + mEdgeBackPlugin); } @@ -945,4 +940,23 @@ public class EdgeBackGestureHandler extends CurrentUserTracker } proto.edgeBackGestureHandler.allowGesture = mAllowGesture; } + + + private static class LogArray extends ArrayDeque<String> { + private final int mLength; + + LogArray(int length) { + mLength = length; + } + + void log(String message) { + if (size() >= mLength) { + removeFirst(); + } + addLast(message); + if (DEBUG_MISSING_GESTURE) { + Log.d(DEBUG_MISSING_GESTURE_TAG, message); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java b/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java index b5ac90828fce..d863dcce4fc7 100644 --- a/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java @@ -240,9 +240,16 @@ public class NotificationHelper { /** Returns whether {@code entry} is suppressed from shade, meaning we should not show it. */ public static boolean shouldFilterOut( Optional<Bubbles> bubblesOptional, NotificationEntry entry) { - return bubblesOptional.isPresent() - && bubblesOptional.get().isBubbleNotificationSuppressedFromShade( - entry.getKey(), entry.getSbn().getGroupKey()); + boolean isSuppressed = false; + //TODO(b/190822282): Investigate what is causing the NullPointerException + try { + isSuppressed = bubblesOptional.isPresent() + && bubblesOptional.get().isBubbleNotificationSuppressedFromShade( + entry.getKey(), entry.getSbn().getGroupKey()); + } catch (Exception e) { + Log.e(TAG, "Exception checking if notification is suppressed: " + e); + } + return isSuppressed; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java index f6d93895ce05..929aedae6706 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java @@ -28,6 +28,7 @@ import android.view.View; import android.widget.TextView; import android.widget.Toast; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto; @@ -73,7 +74,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme private final PageIndicator mPageIndicator; private final View mPowerMenuLite; private final boolean mShowPMLiteButton; - private GlobalActionsDialogLite mGlobalActionsDialog; + private final GlobalActionsDialogLite mGlobalActionsDialog; private final UiEventLogger mUiEventLogger; private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener = @@ -272,7 +273,8 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme private void startSettingsActivity() { ActivityLaunchAnimator.Controller animationController = mSettingsButtonContainer != null ? ActivityLaunchAnimator.Controller.fromView( - mSettingsButtonContainer) : null; + mSettingsButtonContainer, + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON) : null; mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS), true /* dismissShade */, animationController); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index e1a66b2c07ee..7b8a6a0a8d0e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -539,8 +539,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded) { View hostView = mediaHost.getHostView(); - // on keyguard we cross-fade to expanded, so no need to pin it. - if (mLastQSExpansion > 0 && !isKeyguardState()) { + // On keyguard we cross-fade to expanded, so no need to pin it. + // If the collapsed qs isn't visible, we also just keep it at the laid out position. + if (mLastQSExpansion > 0 && !isKeyguardState() && mQqsMediaHost.getVisible()) { float targetPosition = absoluteBottomPosition - getTotalBottomMargin(hostView) - hostView.getHeight(); float currentPosition = mediaHost.getCurrentBounds().top diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 525bad8a0e25..6ddf2a75f491 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -45,7 +45,9 @@ import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.external.CustomTile; +import com.android.systemui.qs.external.CustomTileStatePersister; import com.android.systemui.qs.external.TileLifecycleManager; +import com.android.systemui.qs.external.TileServiceKey; import com.android.systemui.qs.external.TileServices; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.UserTracker; @@ -93,6 +95,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D private final QSLogger mQSLogger; private final UiEventLogger mUiEventLogger; private final InstanceIdSequence mInstanceIdSequence; + private final CustomTileStatePersister mCustomTileStatePersister; private final List<Callback> mCallbacks = new ArrayList<>(); private AutoTileManager mAutoTiles; @@ -119,7 +122,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D QSLogger qsLogger, UiEventLogger uiEventLogger, UserTracker userTracker, - SecureSettings secureSettings) { + SecureSettings secureSettings, + CustomTileStatePersister customTileStatePersister) { mIconController = iconController; mContext = context; mUserContext = context; @@ -139,6 +143,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mDumpManager.registerDumpable(TAG, this); mUserTracker = userTracker; mSecureSettings = secureSettings; + mCustomTileStatePersister = customTileStatePersister; mainHandler.post(() -> { // This is technically a hack to avoid circular dependency of @@ -418,6 +423,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D changeTiles(mTileSpecs, newSpecs); } + /** + * Change the tiles triggered by the user editing. + * <p> + * This is not called on device start, or on user change. + */ public void changeTiles(List<String> previousTiles, List<String> newTiles) { final List<String> copy = new ArrayList<>(previousTiles); final int NP = copy.size(); @@ -433,6 +443,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mBroadcastDispatcher); lifecycleManager.onStopListening(); lifecycleManager.onTileRemoved(); + mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser)); TileLifecycleManager.setTileAdded(mContext, component, false); lifecycleManager.flushMessagesAndUnbind(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 7cc6ecd8cf62..997b96626747 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -399,12 +399,14 @@ public class QuickStatusBarHeader extends FrameLayout { mClockIconsSeparatorLayoutParams.width = 0; setSeparatorVisibility(false); mShowClockIconsSeparator = false; + mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE); } else { datePrivacySeparatorLayoutParams.width = topCutout.width(); mDatePrivacySeparator.setVisibility(View.VISIBLE); mClockIconsSeparatorLayoutParams.width = topCutout.width(); mShowClockIconsSeparator = true; setSeparatorVisibility(mKeyguardExpansionFraction == 0f); + mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ON); } } mDatePrivacySeparator.setLayoutParams(datePrivacySeparatorLayoutParams); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 10eea828bcb4..396eca5c1bee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -46,6 +46,7 @@ import android.widget.Switch; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -85,6 +86,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private final IQSTileService mService; private final TileServiceManager mServiceManager; private final int mUser; + private final CustomTileStatePersister mCustomTileStatePersister; private android.graphics.drawable.Icon mDefaultIcon; private CharSequence mDefaultLabel; @@ -94,6 +96,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private boolean mIsTokenGranted; private boolean mIsShowingDialog; + private final TileServiceKey mKey; + private CustomTile( QSHost host, Looper backgroundLooper, @@ -104,7 +108,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener ActivityStarter activityStarter, QSLogger qsLogger, String action, - Context userContext + Context userContext, + CustomTileStatePersister customTileStatePersister ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); @@ -113,15 +118,29 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mTile = new Tile(); mUserContext = userContext; mUser = mUserContext.getUserId(); - updateDefaultTileAndIcon(); + mKey = new TileServiceKey(mComponent, mUser); + mServiceManager = host.getTileServices().getTileWrapper(this); + mService = mServiceManager.getTileService(); + mCustomTileStatePersister = customTileStatePersister; + } + + @Override + protected void handleInitialize() { + updateDefaultTileAndIcon(); if (mServiceManager.isToggleableTile()) { // Replace states with BooleanState resetStates(); } - - mService = mServiceManager.getTileService(); mServiceManager.setTileChangeListener(this); + if (mServiceManager.isActiveTile()) { + Tile t = mCustomTileStatePersister.readState(mKey); + if (t != null) { + applyTileState(t, /* overwriteNulls */ false); + mServiceManager.clearPendingBind(); + refreshState(); + } + } } @Override @@ -191,7 +210,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener @Override public void onTileChanged(ComponentName tile) { - updateDefaultTileAndIcon(); + mHandler.post(this::updateDefaultTileAndIcon); } @Override @@ -213,16 +232,44 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener } public Tile getQsTile() { + // TODO(b/191145007) Move to background thread safely updateDefaultTileAndIcon(); return mTile; } - public void updateState(Tile tile) { - mTile.setIcon(tile.getIcon()); - mTile.setLabel(tile.getLabel()); - mTile.setSubtitle(tile.getSubtitle()); - mTile.setContentDescription(tile.getContentDescription()); - mTile.setStateDescription(tile.getStateDescription()); + /** + * Update state of {@link this#mTile} from a remote {@link TileService}. + * @param tile tile populated with state to apply + */ + public void updateTileState(Tile tile) { + // This comes from a binder call IQSService.updateQsTile + mHandler.post(() -> handleUpdateTileState(tile)); + } + + private void handleUpdateTileState(Tile tile) { + applyTileState(tile, /* overwriteNulls */ true); + if (mServiceManager.isActiveTile()) { + mCustomTileStatePersister.persistState(mKey, tile); + } + } + + @WorkerThread + private void applyTileState(Tile tile, boolean overwriteNulls) { + if (tile.getIcon() != null || overwriteNulls) { + mTile.setIcon(tile.getIcon()); + } + if (tile.getLabel() != null || overwriteNulls) { + mTile.setLabel(tile.getLabel()); + } + if (tile.getSubtitle() != null || overwriteNulls) { + mTile.setSubtitle(tile.getSubtitle()); + } + if (tile.getContentDescription() != null || overwriteNulls) { + mTile.setContentDescription(tile.getContentDescription()); + } + if (tile.getStateDescription() != null || overwriteNulls) { + mTile.setStateDescription(tile.getStateDescription()); + } mTile.setState(tile.getState()); } @@ -459,6 +506,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener final StatusBarStateController mStatusBarStateController; final ActivityStarter mActivityStarter; final QSLogger mQSLogger; + final CustomTileStatePersister mCustomTileStatePersister; Context mUserContext; String mSpec = ""; @@ -472,7 +520,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, - QSLogger qsLogger + QSLogger qsLogger, + CustomTileStatePersister customTileStatePersister ) { mQSHostLazy = hostLazy; mBackgroundLooper = backgroundLooper; @@ -482,6 +531,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mStatusBarStateController = statusBarStateController; mActivityStarter = activityStarter; mQSLogger = qsLogger; + mCustomTileStatePersister = customTileStatePersister; } Builder setSpec(@NonNull String spec) { @@ -509,7 +559,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mActivityStarter, mQSLogger, action, - mUserContext + mUserContext, + mCustomTileStatePersister ); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt new file mode 100644 index 000000000000..021e632810f9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2021 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.qs.external + +import android.content.ComponentName +import android.content.Context +import android.service.quicksettings.Tile +import android.util.Log +import com.android.internal.annotations.VisibleForTesting +import org.json.JSONException +import org.json.JSONObject +import javax.inject.Inject + +data class TileServiceKey(val componentName: ComponentName, val user: Int) { + private val string = "${componentName.flattenToString()}:$user" + override fun toString() = string +} +private const val STATE = "state" +private const val LABEL = "label" +private const val SUBTITLE = "subtitle" +private const val CONTENT_DESCRIPTION = "content_description" +private const val STATE_DESCRIPTION = "state_description" + +/** + * Persists and retrieves state for [CustomTile]. + * + * This class will persists to a fixed [SharedPreference] file a state for a pair of [ComponentName] + * and user id ([TileServiceKey]). + * + * It persists the state from a [Tile] necessary to present the view in the same state when + * retrieved, with the exception of the icon. + */ +class CustomTileStatePersister @Inject constructor(context: Context) { + companion object { + private const val FILE_NAME = "custom_tiles_state" + } + + private val sharedPreferences = context.getSharedPreferences(FILE_NAME, 0) + + /** + * Read the state from [SharedPreferences]. + * + * Returns `null` if the tile has no saved state. + * + * Any fields that have not been saved will be set to `null` + */ + fun readState(key: TileServiceKey): Tile? { + val state = sharedPreferences.getString(key.toString(), null) ?: return null + return try { + readTileFromString(state) + } catch (e: JSONException) { + Log.e("TileServicePersistence", "Bad saved state: $state", e) + null + } + } + + /** + * Persists the state into [SharedPreferences]. + * + * The implementation does not store fields that are `null` or icons. + */ + fun persistState(key: TileServiceKey, tile: Tile) { + val state = writeToString(tile) + + sharedPreferences.edit().putString(key.toString(), state).apply() + } + + /** + * Removes the state for a given tile, user pair. + * + * Used when the tile is removed by the user. + */ + fun removeState(key: TileServiceKey) { + sharedPreferences.edit().remove(key.toString()).apply() + } +} + +@VisibleForTesting +internal fun readTileFromString(stateString: String): Tile { + val json = JSONObject(stateString) + return Tile().apply { + state = json.getInt(STATE) + label = json.getStringOrNull(LABEL) + subtitle = json.getStringOrNull(SUBTITLE) + contentDescription = json.getStringOrNull(CONTENT_DESCRIPTION) + stateDescription = json.getStringOrNull(STATE_DESCRIPTION) + } +} + +// Properties with null values will not be saved to the Json string in any way. This makes sure +// to properly retrieve a null in that case. +private fun JSONObject.getStringOrNull(name: String): String? { + return if (has(name)) getString(name) else null +} + +@VisibleForTesting +internal fun writeToString(tile: Tile): String { + // Not storing the icon + return with(tile) { + JSONObject() + .put(STATE, state) + .put(LABEL, label) + .put(SUBTITLE, subtitle) + .put(CONTENT_DESCRIPTION, contentDescription) + .put(STATE_DESCRIPTION, stateDescription) + .toString() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index 35cf2a12745e..a7cd11314d7e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -204,7 +204,7 @@ public class TileServices extends IQSService.Stub { tileServiceManager.clearPendingBind(); tileServiceManager.setLastUpdate(System.currentTimeMillis()); } - customTile.updateState(tile); + customTile.updateTileState(tile); customTile.refreshState(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 8f7c493417ec..842fd6c62d06 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -160,7 +160,8 @@ public class QSFactoryImpl implements QSFactory { public QSTile createTile(String tileSpec) { QSTileImpl tile = createTileInternal(tileSpec); if (tile != null) { - tile.handleStale(); // Tile was just created, must be stale. + tile.initialize(); + tile.postStale(); // Tile was just created, must be stale. } return tile; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index a938821a343f..4616be8f7937 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -50,6 +50,7 @@ import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.InstanceId; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; @@ -158,6 +159,15 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy */ abstract public int getMetricsCategory(); + /** + * Performs initialization of the tile + * + * Use this to perform initialization of the tile. Empty by default. + */ + protected void handleInitialize() { + + } + protected QSTileImpl( QSHost host, Looper backgroundLooper, @@ -346,6 +356,15 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mHandler.sendEmptyMessage(H.DESTROY); } + /** + * Schedules initialization of the tile. + * + * Should be called upon creation of the tile, before performing other operations + */ + public void initialize() { + mHandler.sendEmptyMessage(H.INITIALIZE); + } + public TState getState() { return mState; } @@ -370,6 +389,13 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } /** + * Posts a stale message to the background thread. + */ + public void postStale() { + mHandler.sendEmptyMessage(H.STALE); + } + + /** * Handles secondary click on the tile. * * Defaults to {@link QSTileImpl#handleClick} @@ -389,7 +415,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy */ protected void handleLongClick(@Nullable View view) { ActivityLaunchAnimator.Controller animationController = - view != null ? ActivityLaunchAnimator.Controller.fromView(view) : null; + view != null ? ActivityLaunchAnimator.Controller.fromView(view, + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) : null; mActivityStarter.postStartActivityDismissingKeyguard(getLongClickIntent(), 0, animationController); } @@ -580,6 +607,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy private static final int SET_LISTENING = 13; @VisibleForTesting protected static final int STALE = 14; + private static final int INITIALIZE = 15; @VisibleForTesting protected H(Looper looper) { @@ -638,6 +666,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } else if (msg.what == STALE) { name = "handleStale"; handleStale(); + } else if (msg.what == INITIALIZE) { + name = "initialize"; + handleInitialize(); } else { throw new IllegalArgumentException("Unknown msg: " + msg.what); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt index 69d49d44f822..73d13700d61b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt @@ -11,6 +11,7 @@ import android.text.TextUtils import android.text.format.DateFormat import android.view.View import androidx.annotation.VisibleForTesting +import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.MetricsLogger import com.android.systemui.R import com.android.systemui.animation.ActivityLaunchAnimator @@ -70,7 +71,10 @@ class AlarmTile @Inject constructor( } override fun handleClick(view: View?) { - val animationController = view?.let { ActivityLaunchAnimator.Controller.fromView(it) } + val animationController = view?.let { + ActivityLaunchAnimator.Controller.fromView( + it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) + } val pendingIntent = lastAlarmInfo?.showIntent if (pendingIntent != null) { mActivityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt index 6d3190ffa725..f66b7226fbae 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt @@ -22,6 +22,7 @@ import android.os.Handler import android.os.Looper import android.service.quicksettings.Tile import android.view.View +import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.MetricsLogger import com.android.systemui.R import com.android.systemui.animation.ActivityLaunchAnimator @@ -106,7 +107,8 @@ class DeviceControlsTile @Inject constructor( putExtra(ControlsUiController.EXTRA_ANIMATE, true) } val animationController = view?.let { - ActivityLaunchAnimator.Controller.fromView(it) + ActivityLaunchAnimator.Controller.fromView( + it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) } mUiHandler.post { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index 0e4434baa0e8..98cd88af232f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -37,6 +37,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.animation.ActivityLaunchAnimator; @@ -120,7 +121,8 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { @Override protected void handleClick(@Nullable View view) { ActivityLaunchAnimator.Controller animationController = - view == null ? null : ActivityLaunchAnimator.Controller.fromView(view); + view == null ? null : ActivityLaunchAnimator.Controller.fromView(view, + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE); mUiHandler.post(() -> { if (mSelectedCard != null) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java index 9e11451afa06..0a60f6da159e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java @@ -62,6 +62,7 @@ public class CropView extends View { private final float mCropTouchMargin; private final Paint mShadePaint; private final Paint mHandlePaint; + private final Paint mContainerBackgroundPaint; // Crop rect with each element represented as [0,1] along its proper axis. private RectF mCrop = new RectF(0, 0, 1, 1); @@ -79,6 +80,9 @@ public class CropView extends View { // The allowable values for the current boundary being dragged private Range<Float> mMotionRange; + // Value [0,1] indicating progress in animateEntrance() + private float mEntranceInterpolation = 1f; + private CropInteractionListener mCropInteractionListener; private final ExploreByTouchHelper mExploreByTouchHelper; @@ -92,6 +96,9 @@ public class CropView extends View { attrs, R.styleable.CropView, 0, 0); mShadePaint = new Paint(); mShadePaint.setColor(t.getColor(R.styleable.CropView_scrimColor, Color.TRANSPARENT)); + mContainerBackgroundPaint = new Paint(); + mContainerBackgroundPaint.setColor(t.getColor(R.styleable.CropView_containerBackgroundColor, + Color.TRANSPARENT)); mHandlePaint = new Paint(); mHandlePaint.setColor(t.getColor(R.styleable.CropView_handleColor, Color.BLACK)); mHandlePaint.setStrokeCap(Paint.Cap.ROUND); @@ -125,10 +132,22 @@ public class CropView extends View { @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); - drawShade(canvas, 0, 0, 1, mCrop.top); - drawShade(canvas, 0, mCrop.bottom, 1, 1); + // Top and bottom borders reflect the boundary between the (scrimmed) image and the + // opaque container background. This is only meaningful during an entrance transition. + float topBorder = MathUtils.lerp(mCrop.top, 0, mEntranceInterpolation); + float bottomBorder = MathUtils.lerp(mCrop.bottom, 1, mEntranceInterpolation); + drawShade(canvas, 0, topBorder, 1, mCrop.top); + drawShade(canvas, 0, mCrop.bottom, 1, bottomBorder); drawShade(canvas, 0, mCrop.top, mCrop.left, mCrop.bottom); drawShade(canvas, mCrop.right, mCrop.top, 1, mCrop.bottom); + + // Entrance transition expects the crop bounds to be full width, so we only draw container + // background on the top and bottom. + drawContainerBackground(canvas, 0, 0, 1, topBorder); + drawContainerBackground(canvas, 0, bottomBorder, 1, 1); + + mHandlePaint.setAlpha((int) (mEntranceInterpolation * 255)); + drawHorizontalHandle(canvas, mCrop.top, /* draw the handle tab up */ true); drawHorizontalHandle(canvas, mCrop.bottom, /* draw the handle tab down */ false); drawVerticalHandle(canvas, mCrop.left, /* left */ true); @@ -282,6 +301,22 @@ public class CropView extends View { } /** + * Fade in crop bounds, animate reveal of cropped-out area from current crop bounds. + */ + public void animateEntrance() { + mEntranceInterpolation = 0; + ValueAnimator animator = new ValueAnimator(); + animator.addUpdateListener(animation -> { + mEntranceInterpolation = animation.getAnimatedFraction(); + invalidate(); + }); + animator.setFloatValues(0f, 1f); + animator.setDuration(750); + animator.setInterpolator(new FastOutSlowInInterpolator()); + animator.start(); + } + + /** * Set additional top and bottom padding for the image being cropped (used when the * corresponding ImageView doesn't take the full height). */ @@ -369,6 +404,13 @@ public class CropView extends View { fractionToVerticalPixels(bottom), mShadePaint); } + private void drawContainerBackground(Canvas canvas, float left, float top, float right, + float bottom) { + canvas.drawRect(fractionToHorizontalPixels(left), fractionToVerticalPixels(top), + fractionToHorizontalPixels(right), + fractionToVerticalPixels(bottom), mContainerBackgroundPaint); + } + private void drawHorizontalHandle(Canvas canvas, float frac, boolean handleTabUp) { int y = fractionToVerticalPixels(frac); canvas.drawLine(fractionToHorizontalPixels(mCrop.left), y, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index d5b4032b1c0f..25ec1d74008e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -192,7 +192,6 @@ public class LongScreenshotActivity extends Activity { mLongScreenshot = longScreenshot; Drawable drawable = mLongScreenshot.getDrawable(); mPreview.setImageDrawable(drawable); - mCropView.setVisibility(View.VISIBLE); mMagnifierView.setDrawable(mLongScreenshot.getDrawable(), mLongScreenshot.getWidth(), mLongScreenshot.getHeight()); // Original boundaries go from the image tile set's y=0 to y=pageSize, so @@ -219,10 +218,12 @@ public class LongScreenshotActivity extends Activity { public void onTransitionEnd(Transition transition) { super.onTransitionEnd(transition); mPreview.animate().alpha(1f); - mCropView.animateBoundaryTo( + mCropView.setBoundaryPosition( CropView.CropBoundary.TOP, topFraction); - mCropView.animateBoundaryTo( + mCropView.setBoundaryPosition( CropView.CropBoundary.BOTTOM, bottomFraction); + mCropView.animateEntrance(); + mCropView.setVisibility(View.VISIBLE); setButtonsEnabled(true); mEnterTransitionView.setVisibility(View.GONE); } @@ -250,6 +251,7 @@ public class LongScreenshotActivity extends Activity { Log.d(TAG, "onCachedImageLoaded(imageResult=" + imageResult + ")"); BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap); mPreview.setImageDrawable(drawable); + mPreview.setAlpha(1f); mMagnifierView.setDrawable(drawable, imageResult.bitmap.getWidth(), imageResult.bitmap.getHeight()); mCropView.setVisibility(View.VISIBLE); @@ -476,19 +478,21 @@ public class LongScreenshotActivity extends Activity { params.height = boundaries.height(); mTransitionView.setLayoutParams(params); - ConstraintLayout.LayoutParams enterTransitionParams = - (ConstraintLayout.LayoutParams) mEnterTransitionView.getLayoutParams(); - float topFraction = Math.max(0, - -mLongScreenshot.getTop() / (float) mLongScreenshot.getHeight()); - enterTransitionParams.width = (int) (scale * drawable.getIntrinsicWidth()); - enterTransitionParams.height = (int) (scale * mLongScreenshot.getPageHeight()); - mEnterTransitionView.setLayoutParams(enterTransitionParams); - - Matrix matrix = new Matrix(); - matrix.setScale(scale, scale); - matrix.postTranslate(0, -scale * drawable.getIntrinsicHeight() * topFraction); - mEnterTransitionView.setImageMatrix(matrix); - mEnterTransitionView.setTranslationY( - topFraction * previewHeight + mPreview.getPaddingTop() + extraPadding); + if (mLongScreenshot != null) { + ConstraintLayout.LayoutParams enterTransitionParams = + (ConstraintLayout.LayoutParams) mEnterTransitionView.getLayoutParams(); + float topFraction = Math.max(0, + -mLongScreenshot.getTop() / (float) mLongScreenshot.getHeight()); + enterTransitionParams.width = (int) (scale * drawable.getIntrinsicWidth()); + enterTransitionParams.height = (int) (scale * mLongScreenshot.getPageHeight()); + mEnterTransitionView.setLayoutParams(enterTransitionParams); + + Matrix matrix = new Matrix(); + matrix.setScale(scale, scale); + matrix.postTranslate(0, -scale * drawable.getIntrinsicHeight() * topFraction); + mEnterTransitionView.setImageMatrix(matrix); + mEnterTransitionView.setTranslationY( + topFraction * previewHeight + mPreview.getPaddingTop() + extraPadding); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 91a0e6fedef8..0bb702f6c9e4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -112,6 +112,7 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private ViewGroup mIndicationArea; private KeyguardIndicationTextView mTopIndicationView; + private KeyguardIndicationTextView mLockScreenIndicationView; private final IBatteryStats mBatteryInfo; private final SettableWakeLock mWakeLock; private final DockManager mDockManager; @@ -208,17 +209,21 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal mKeyguardUpdateMonitor.registerCallback(mTickReceiver); mStatusBarStateController.addCallback(mStatusBarStateListener); mKeyguardStateController.addCallback(this); + + mStatusBarStateListener.onDozingChanged(mStatusBarStateController.isDozing()); } public void setIndicationArea(ViewGroup indicationArea) { mIndicationArea = indicationArea; mTopIndicationView = indicationArea.findViewById(R.id.keyguard_indication_text); + mLockScreenIndicationView = indicationArea.findViewById( + R.id.keyguard_indication_text_bottom); mInitialTextColorState = mTopIndicationView != null ? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE); mRotateTextViewController = new KeyguardIndicationRotateTextViewController( - indicationArea.findViewById(R.id.keyguard_indication_text_bottom), - mExecutor, - mStatusBarStateController); + mLockScreenIndicationView, + mExecutor, + mStatusBarStateController); updateIndication(false /* animate */); updateDisclosure(); if (mBroadcastReceiver == null) { @@ -630,6 +635,7 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal // should be shown based on user or device state // AoD if (mDozing) { + mLockScreenIndicationView.setVisibility(View.GONE); mTopIndicationView.setVisibility(VISIBLE); // When dozing we ignore any text color and use white instead, because // colors can be hard to read in low brightness. @@ -659,6 +665,8 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal // LOCK SCREEN mTopIndicationView.setVisibility(GONE); + mTopIndicationView.setText(null); + mLockScreenIndicationView.setVisibility(View.VISIBLE); updateIndications(animate, KeyguardUpdateMonitor.getCurrentUser()); } @@ -914,7 +922,8 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) { mStatusBarKeyguardViewManager.showBouncerMessage(errString, mInitialTextColorState); } else if (mKeyguardUpdateMonitor.isScreenOn()) { - showTransientIndication(errString); + showTransientIndication(errString, /* isError */ true, + /* hideOnScreenOff */ true); // We want to keep this message around in case the screen was off hideTransientIndicationDelayed(HIDE_DELAY_MS); } else { @@ -1032,9 +1041,8 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal if (mHideTransientMessageOnScreenOff && mDozing) { hideTransientIndication(); - } else { - updateIndication(false); } + updateIndication(false); } }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 6a5f001ac2ee..ec648ad519a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -93,10 +93,10 @@ class CircleReveal( val endRadius: Float ) : LightRevealEffect { override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) { - val interpolatedAmount = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(amount) - val fadeAmount = - LightRevealEffect.getPercentPastThreshold(interpolatedAmount, 0.75f) - val radius = startRadius + ((endRadius - startRadius) * interpolatedAmount) + // reveal amount updates already have an interpolator, so we intentionally use the + // non-interpolated amount + val fadeAmount = LightRevealEffect.getPercentPastThreshold(amount, 0.5f) + val radius = startRadius + ((endRadius - startRadius) * amount) scrim.revealGradientEndColorAlpha = 1f - fadeAmount scrim.setRevealGradientBounds( centerX - radius /* left */, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 9f59023f1890..f8a1ff879e72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -145,11 +145,11 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll } @Override - public boolean setState(int state) { + public boolean setState(int state, boolean force) { if (state > MAX_STATE || state < MIN_STATE) { throw new IllegalArgumentException("Invalid state " + state); } - if (state == mState) { + if (!force && state == mState) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java index b6d6ed53b681..73f3d90bd4f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java @@ -59,7 +59,19 @@ public interface SysuiStatusBarStateController extends StatusBarStateController * @param state see {@link StatusBarState} for valid options * @return {@code true} if the state changed, else {@code false} */ - boolean setState(int state); + default boolean setState(int state) { + return setState(state, false /* force */); + } + + /** + * Update the status bar state + * @param state see {@link StatusBarState} for valid options + * @param force whether to set the state even if it's the same as the current state. This will + * dispatch the state to all StatusBarStateListeners, ensuring that all listening + * components are reset to this state. + * @return {@code true} if the state was changed or set forcefully + */ + boolean setState(int state, boolean force); /** * Update the dozing state from {@link StatusBar}'s perspective diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt index 8479b30c3a75..f30010cf4d1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt @@ -383,10 +383,18 @@ const val ANIMATING_OUT = 3 const val SHOWING_PERSISTENT_DOT = 4 private const val TAG = "SystemStatusAnimationScheduler" -private const val DELAY: Long = 100 -private const val DISPLAY_LENGTH = 5000L -private const val ENTRANCE_ANIM_LENGTH = 500L -private const val CHIP_ANIM_LENGTH = 500L +private const val DELAY = 0L + +/** + * The total time spent animation should be 1500ms. The entrance animation is how much time + * we give to the system to animate system elements out of the way. Total chip animation length + * will be equivalent to 2*chip_anim_length + display_length + */ +private const val ENTRANCE_ANIM_LENGTH = 250L +private const val CHIP_ANIM_LENGTH = 250L +// 1s + entrance time + chip anim_length +private const val DISPLAY_LENGTH = 1500L + private const val MIN_UPTIME: Long = 5 * 1000 private const val DEBUG = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java index aef01e9bd811..0fb1c54bb150 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -71,11 +71,11 @@ public final class NotificationClicker implements View.OnClickListener { // Check if the notification is displaying the menu, if so slide notification back if (isMenuVisible(row)) { mLogger.logMenuVisible(entry); - row.animateTranslateNotification(0); + row.animateResetTranslation(); return; } else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) { mLogger.logParentMenuVisible(entry); - row.getNotificationParent().animateTranslateNotification(0); + row.getNotificationParent().animateResetTranslation(); return; } else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) { // We never want to open the app directly if the user clicks in between diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 760bee21b0d1..b0a7767accfc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -264,7 +264,7 @@ class NotificationWakeUpCoordinator @Inject constructor( } override fun onStateChanged(newState: Int) { - if (unlockedScreenOffAnimationController.shouldPlayScreenOffAnimation()) { + if (dozeParameters.shouldControlUnlockedScreenOff()) { if (unlockedScreenOffAnimationController.isScreenOffAnimationPlaying() && state == StatusBarState.KEYGUARD && newState == StatusBarState.SHADE) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 413662447028..c24c2be3faa3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -330,30 +330,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } - @Override - public void setDistanceToTopRoundness(float distanceToTopRoundness) { - super.setDistanceToTopRoundness(distanceToTopRoundness); - mBackgroundNormal.setDistanceToTopRoundness(distanceToTopRoundness); - } - - /** Sets whether this view is the last notification in a section. */ - @Override - public void setLastInSection(boolean lastInSection) { - if (lastInSection != mLastInSection) { - super.setLastInSection(lastInSection); - mBackgroundNormal.setLastInSection(lastInSection); - } - } - - /** Sets whether this view is the first notification in a section. */ - @Override - public void setFirstInSection(boolean firstInSection) { - if (firstInSection != mFirstInSection) { - super.setFirstInSection(firstInSection); - mBackgroundNormal.setFirstInSection(firstInSection); - } - } - /** * Set an override tint color that is used for the background. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 6fd556763943..ba28dc59def4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -846,8 +846,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView updateClickAndFocus(); if (mNotificationParent != null) { setOverrideTintColor(NO_COLOR, 0.0f); - // Let's reset the distance to top roundness, as this isn't applied to group children - setDistanceToTopRoundness(NO_ROUNDNESS); mNotificationParent.updateBackgroundForGroupState(); } updateBackgroundClipping(); @@ -876,7 +874,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected boolean handleSlideBack() { if (mMenuRow != null && mMenuRow.isMenuVisible()) { - animateTranslateNotification(0 /* targetLeft */); + animateResetTranslation(); return true; } return false; @@ -1713,21 +1711,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this); mChildrenContainer.onNotificationUpdated(); - if (mShouldTranslateContents) { - mTranslateableViews.add(mChildrenContainer); - } + mTranslateableViews.add(mChildrenContainer); }); - if (mShouldTranslateContents) { - // Add the views that we translate to reveal the menu - mTranslateableViews = new ArrayList<>(); - for (int i = 0; i < getChildCount(); i++) { - mTranslateableViews.add(getChildAt(i)); - } - // Remove views that don't translate - mTranslateableViews.remove(mChildrenContainerStub); - mTranslateableViews.remove(mGutsStub); + // Add the views that we translate to reveal the menu + mTranslateableViews = new ArrayList<>(); + for (int i = 0; i < getChildCount(); i++) { + mTranslateableViews.add(getChildAt(i)); } + // Remove views that don't translate + mTranslateableViews.remove(mChildrenContainerStub); + mTranslateableViews.remove(mGutsStub); } private void doLongClickCallback() { @@ -1805,7 +1799,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mTranslateAnim.cancel(); } - if (!mShouldTranslateContents) { + if (mDismissUsingRowTranslationX) { setTranslationX(0); } else if (mTranslateableViews != null) { for (int i = 0; i < mTranslateableViews.size(); i++) { @@ -1867,23 +1861,47 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mPrivateLayout.getActiveRemoteInputText(); } - public void animateTranslateNotification(final float leftTarget) { + /** + * Reset the translation with an animation. + */ + public void animateResetTranslation() { if (mTranslateAnim != null) { mTranslateAnim.cancel(); } - mTranslateAnim = getTranslateViewAnimator(leftTarget, null /* updateListener */); + mTranslateAnim = getTranslateViewAnimator(0, null /* updateListener */); if (mTranslateAnim != null) { mTranslateAnim.start(); } } + /** + * Set the dismiss behavior of the view. + * @param usingRowTranslationX {@code true} if the view should translate using regular + * translationX, otherwise the contents will be + * translated. + */ + @Override + public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) { + if (usingRowTranslationX != mDismissUsingRowTranslationX) { + // In case we were already transitioning, let's switch over! + float previousTranslation = getTranslation(); + if (previousTranslation != 0) { + setTranslation(0); + } + super.setDismissUsingRowTranslationX(usingRowTranslationX); + if (previousTranslation != 0) { + setTranslation(previousTranslation); + } + } + } + @Override public void setTranslation(float translationX) { invalidate(); if (isBlockingHelperShowingAndTranslationFinished()) { mGuts.setTranslationX(translationX); return; - } else if (!mShouldTranslateContents) { + } else if (mDismissUsingRowTranslationX) { setTranslationX(translationX); } else if (mTranslateableViews != null) { // Translate the group of views @@ -1907,7 +1925,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public float getTranslation() { - if (!mShouldTranslateContents) { + if (mDismissUsingRowTranslationX) { return getTranslationX(); } @@ -2898,7 +2916,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView float y = event.getY(); NotificationViewWrapper wrapper = getVisibleNotificationViewWrapper(); NotificationHeaderView header = wrapper == null ? null : wrapper.getNotificationHeader(); - if (header != null && header.isInTouchRect(x - getTranslation(), y)) { + // the extra translation only needs to be added, if we're translating the notification + // contents, otherwise the motionEvent is already at the right place due to the + // touch event system. + float translation = !mDismissUsingRowTranslationX ? getTranslation() : 0; + if (header != null && header.isInTouchRect(x - translation, y)) { return true; } if ((!mIsSummaryWithChildren || shouldShowPublic()) @@ -3037,24 +3059,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override - public boolean topAmountNeedsClipping() { - if (isGroupExpanded()) { - return true; - } - if (isGroupExpansionChanging()) { - return true; - } - if (getShowingLayout().shouldClipToRounding(true /* topRounded */, - false /* bottomRounded */)) { - return true; - } - if (mGuts != null && mGuts.getAlpha() != 0.0f) { - return true; - } - return false; - } - - @Override protected boolean childNeedsClipping(View child) { if (child instanceof NotificationContentView) { NotificationContentView contentView = (NotificationContentView) child; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java index 5134c62dc182..d58fe3b3c4a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java @@ -71,21 +71,19 @@ public abstract class ExpandableOutlineView extends ExpandableView { private int mBackgroundTop; /** - * {@code true} if the children views of the {@link ExpandableOutlineView} are translated when + * {@code false} if the children views of the {@link ExpandableOutlineView} are translated when * it is moved. Otherwise, the translation is set on the {@code ExpandableOutlineView} itself. */ - protected boolean mShouldTranslateContents; - private boolean mTopAmountRounded; - private float mDistanceToTopRoundness = -1; + protected boolean mDismissUsingRowTranslationX = true; private float[] mTmpCornerRadii = new float[8]; private final ViewOutlineProvider mProvider = new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { if (!mCustomOutline && getCurrentTopRoundness() == 0.0f - && getCurrentBottomRoundness() == 0.0f && !mAlwaysRoundBothCorners - && !mTopAmountRounded) { - int translation = mShouldTranslateContents ? (int) getTranslation() : 0; + && getCurrentBottomRoundness() == 0.0f && !mAlwaysRoundBothCorners) { + // Only when translating just the contents, does the outline need to be shifted. + int translation = !mDismissUsingRowTranslationX ? (int) getTranslation() : 0; int left = Math.max(translation, 0); int top = mClipTopAmount + mBackgroundTop; int right = getWidth() + Math.min(translation, 0); @@ -110,7 +108,9 @@ public abstract class ExpandableOutlineView extends ExpandableView { float topRoundness = mAlwaysRoundBothCorners ? mOutlineRadius : getCurrentBackgroundRadiusTop(); if (!mCustomOutline) { - int translation = mShouldTranslateContents && !ignoreTranslation + // The outline just needs to be shifted if we're translating the contents. Otherwise + // it's already in the right place. + int translation = !mDismissUsingRowTranslationX && !ignoreTranslation ? (int) getTranslation() : 0; int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f); left = Math.max(translation, 0) - halfExtraWidth; @@ -168,33 +168,15 @@ public abstract class ExpandableOutlineView extends ExpandableView { @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { canvas.save(); - Path intersectPath = null; - if (mTopAmountRounded && topAmountNeedsClipping()) { - int left = (int) (- mExtraWidthForClipping / 2.0f); - int top = (int) (mClipTopAmount - mDistanceToTopRoundness); - int right = getWidth() + (int) (mExtraWidthForClipping + left); - int bottom = (int) Math.max(mMinimumHeightForClipping, - Math.max(getActualHeight() - mClipBottomAmount, top + mOutlineRadius)); - getRoundedRectPath(left, top, right, bottom, mOutlineRadius, 0.0f, mClipPath); - intersectPath = mClipPath; - } - boolean clipped = false; if (childNeedsClipping(child)) { Path clipPath = getCustomClipPath(child); if (clipPath == null) { clipPath = getClipPath(false /* ignoreTranslation */); } if (clipPath != null) { - if (intersectPath != null) { - clipPath.op(intersectPath, Path.Op.INTERSECT); - } canvas.clipPath(clipPath); - clipped = true; } } - if (!clipped && intersectPath != null) { - canvas.clipPath(intersectPath); - } boolean result = super.drawChild(canvas, child, drawingTime); canvas.restore(); return result; @@ -212,32 +194,19 @@ public abstract class ExpandableOutlineView extends ExpandableView { invalidate(); } - @Override - public void setDistanceToTopRoundness(float distanceToTopRoundness) { - super.setDistanceToTopRoundness(distanceToTopRoundness); - if (distanceToTopRoundness != mDistanceToTopRoundness) { - mTopAmountRounded = distanceToTopRoundness >= 0; - mDistanceToTopRoundness = distanceToTopRoundness; - applyRoundness(); - } - } - protected boolean childNeedsClipping(View child) { return false; } - public boolean topAmountNeedsClipping() { - return true; - } - protected boolean isClippingNeeded() { - return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ; + // When translating the contents instead of the overall view, we need to make sure we clip + // rounded to the contents. + boolean forTranslation = getTranslation() != 0 && !mDismissUsingRowTranslationX; + return mAlwaysRoundBothCorners || mCustomOutline || forTranslation; } private void initDimens() { Resources res = getResources(); - mShouldTranslateContents = - res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe); mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius); mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline); if (!mAlwaysRoundBothCorners) { @@ -272,11 +241,6 @@ public abstract class ExpandableOutlineView extends ExpandableView { } public float getCurrentBackgroundRadiusTop() { - // If this view is top amount notification view, it should always has round corners on top. - // It will be applied with applyRoundness() - if (mTopAmountRounded) { - return mOutlineRadius; - } return getCurrentTopRoundness() * mOutlineRadius; } @@ -382,9 +346,25 @@ public abstract class ExpandableOutlineView extends ExpandableView { } } + /** + * Set the dismiss behavior of the view. + * @param usingRowTranslationX {@code true} if the view should translate using regular + * translationX, otherwise the contents will be + * translated. + */ + public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) { + mDismissUsingRowTranslationX = usingRowTranslationX; + } + @Override public int getOutlineTranslation() { - return mCustomOutline ? mOutlineRect.left : (int) getTranslation(); + if (mCustomOutline) { + return mOutlineRect.left; + } + if (mDismissUsingRowTranslationX) { + return 0; + } + return (int) getTranslation(); } public void updateOutline() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 763d197847c3..8b0764b1c313 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -46,7 +46,6 @@ import java.util.List; public abstract class ExpandableView extends FrameLayout implements Dumpable { private static final String TAG = "ExpandableView"; - public static final float NO_ROUNDNESS = -1; protected OnHeightChangedListener mOnHeightChangedListener; private int mActualHeight; protected int mClipTopAmount; @@ -192,14 +191,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { } } - /** - * Set the distance to the top roundness, from where we should start clipping a value above - * or equal to 0 is the effective distance, and if a value below 0 is received, there should - * be no clipping. - */ - public void setDistanceToTopRoundness(float distanceToTopRoundness) { - } - public void setActualHeight(int actualHeight) { setActualHeight(actualHeight, true /* notifyListeners */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java index 4b1f679b8851..754de580cd61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java @@ -42,10 +42,8 @@ public class NotificationBackgroundView extends View { private int mActualHeight; private int mClipBottomAmount; private int mTintColor; - private float[] mCornerRadii = new float[8]; + private final float[] mCornerRadii = new float[8]; private boolean mBottomIsRounded; - private boolean mLastInSection; - private boolean mFirstInSection; private int mBackgroundTop; private boolean mBottomAmountClips = true; private boolean mExpandAnimationRunning; @@ -53,9 +51,6 @@ public class NotificationBackgroundView extends View { private int mDrawableAlpha = 255; private boolean mIsPressedAllowed; - private boolean mTopAmountRounded; - private float mDistanceToTopRoundness; - public NotificationBackgroundView(Context context, AttributeSet attrs) { super(context, attrs); mDontModifyCorners = getResources().getBoolean( @@ -90,15 +85,6 @@ public class NotificationBackgroundView extends View { left = (int) ((getWidth() - mActualWidth) / 2.0f); right = (int) (left + mActualWidth); } - if (mTopAmountRounded) { - int clipTop = (int) (mClipTopAmount - mDistanceToTopRoundness); - if (clipTop >= 0 || !mFirstInSection) { - top += clipTop; - } - if (clipTop >= 0 && !mLastInSection) { - bottom += clipTop; - } - } drawable.setBounds(left, top, right, bottom); drawable.draw(canvas); } @@ -180,14 +166,6 @@ public class NotificationBackgroundView extends View { invalidate(); } - public void setDistanceToTopRoundness(float distanceToTopRoundness) { - if (distanceToTopRoundness != mDistanceToTopRoundness) { - mTopAmountRounded = distanceToTopRoundness >= 0; - mDistanceToTopRoundness = distanceToTopRoundness; - invalidate(); - } - } - @Override public boolean hasOverlappingRendering() { @@ -246,18 +224,6 @@ public class NotificationBackgroundView extends View { } } - /** Sets whether this background belongs to the last notification in a section. */ - public void setLastInSection(boolean lastInSection) { - mLastInSection = lastInSection; - invalidate(); - } - - /** Sets whether this background belongs to the first notification in a section. */ - public void setFirstInSection(boolean firstInSection) { - mFirstInSection = firstInSection; - invalidate(); - } - private void updateBackgroundRadii() { if (mDontModifyCorners) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 0c86262d9037..6822d24947c3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -75,7 +75,6 @@ public class AmbientState { private int mExpandAnimationTopChange; private ExpandableNotificationRow mExpandingNotification; private float mHideAmount; - private float mNotificationScrimTop; private boolean mAppearing; private float mPulseHeight = MAX_PULSE_HEIGHT; private float mDozeAmount = 0.0f; @@ -256,20 +255,6 @@ public class AmbientState { return mHideAmount; } - /** - * Set y position of top of notifications background scrim, relative to top of screen. - */ - public void setNotificationScrimTop(float notificationScrimTop) { - mNotificationScrimTop = notificationScrimTop; - } - - /** - * @return Y position of top of notifications background scrim, relative to top of screen. - */ - public float getNotificationScrimTop() { - return mNotificationScrimTop; - } - public void setHideSensitive(boolean hideSensitive) { mHideSensitive = hideSensitive; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index f90b4c079c50..d79c57565dcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -40,6 +40,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Outline; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.PointF; import android.graphics.Rect; import android.os.Bundle; @@ -424,13 +425,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private final Rect mBackgroundAnimationRect = new Rect(); private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>(); private int mHeadsUpInset; + + /** + * The position of the scroll boundary relative to this view. This is where the notifications + * stop scrolling and will start to clip instead. + */ + private int mQsScrollBoundaryPosition; private HeadsUpAppearanceController mHeadsUpAppearanceController; private final Rect mTmpRect = new Rect(); private DismissListener mDismissListener; private DismissAllAnimationListener mDismissAllAnimationListener; private NotificationRemoteInputManager mRemoteInputManager; private ShadeController mShadeController; - private Runnable mOnStackYChanged; + private Consumer<Boolean> mOnStackYChanged; protected boolean mClearAllEnabled; @@ -453,6 +460,38 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private NotificationStackScrollLayoutController mController; private boolean mKeyguardMediaControllorVisible; + + /** + * The clip path used to clip the view in a rounded way. + */ + private final Path mRoundedClipPath = new Path(); + + /** + * Should we use rounded rect clipping right now + */ + private boolean mShouldUseRoundedRectClipping = false; + + private int mRoundedRectClippingLeft; + private int mRoundedRectClippingTop; + private int mRoundedRectClippingBottom; + private int mRoundedRectClippingRight; + private float[] mBgCornerRadii = new float[8]; + + /** + * Whether stackY should be animated in case the view is getting shorter than the scroll + * position and this scrolling will lead to the top scroll inset getting smaller. + */ + private boolean mAnimateStackYForContentHeightChange = false; + + /** + * Are we launching a notification right now + */ + private boolean mLaunchingNotification; + + /** + * Do notifications dismiss with normal transitioning + */ + private boolean mDismissUsingRowTranslationX = true; private NotificationEntry mTopHeadsUpEntry; private long mNumHeadsUp; private NotificationStackScrollLayoutController.TouchHandler mTouchHandler; @@ -506,7 +545,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mSectionsManager = notificationSectionsManager; mFeatureFlags = featureFlags; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - mShouldUseSplitNotificationShade = shouldUseSplitNotificationShade(mFeatureFlags, res); + updateSplitNotificationShade(); mSectionsManager.initialize(this, LayoutInflater.from(context)); mSections = mSectionsManager.createSectionsForBuckets(); @@ -862,6 +901,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mCornerRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius); mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize( R.dimen.heads_up_status_bar_padding); + mQsScrollBoundaryPosition = res.getDimensionPixelSize( + com.android.internal.R.dimen.quick_qs_offset_height); } void updateCornerRadius() { @@ -961,6 +1002,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateFirstAndLastBackgroundViews(); updateAlgorithmLayoutMinHeight(); updateOwnTranslationZ(); + + // Once the layout has finished, we don't need to animate any scrolling clampings anymore. + mAnimateStackYForContentHeightChange = false; } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -1017,33 +1061,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void onPreDrawDuringAnimation() { mShelf.updateAppearance(); - updateClippingToTopRoundedCorner(); if (!mNeedsAnimation && !mChildrenUpdateRequested) { updateBackground(); } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - private void updateClippingToTopRoundedCorner() { - Float clipStart = mAmbientState.getNotificationScrimTop(); - Float clipEnd = clipStart + mCornerRadius; - boolean first = true; - for (int i = 0; i < getChildCount(); i++) { - ExpandableView child = (ExpandableView) getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - float start = child.getTranslationY(); - float end = start + child.getActualHeight(); - boolean clip = clipStart > start && clipStart < end - || clipEnd >= start && clipEnd <= end; - clip &= !(first && mScrollAdapter.isScrolledToTop()); - child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0) - : ExpandableView.NO_ROUNDNESS); - first = false; - } - } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void updateScrollStateForAddedChildren() { if (mChildrenToAddAnimated.isEmpty()) { @@ -1117,7 +1139,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private void clampScrollPosition() { int scrollRange = getScrollRange(); if (scrollRange < mOwnScrollY) { - setOwnScrollY(scrollRange); + boolean animateStackY = false; + if (scrollRange < getScrollAmountToScrollBoundary() + && mAnimateStackYForContentHeightChange) { + // if the scroll boundary updates the position of the stack, + animateStackY = true; + } + setOwnScrollY(scrollRange, animateStackY); } } @@ -1146,6 +1174,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * Apply expansion fraction to the y position and height of the notifications panel. */ private void updateStackPosition() { + updateStackPosition(false /* listenerNeedsAnimation */); + } + + /** + * Apply expansion fraction to the y position and height of the notifications panel. + * @param listenerNeedsAnimation does the listener need to animate? + */ + private void updateStackPosition(boolean listenerNeedsAnimation) { // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition + mAmbientState.getOverExpansion(); @@ -1153,7 +1189,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable final float stackY = MathUtils.lerp(0, endTopPosition, fraction); mAmbientState.setStackY(stackY); if (mOnStackYChanged != null) { - mOnStackYChanged.run(); + mOnStackYChanged.accept(listenerNeedsAnimation); } if (mQsExpansionFraction <= 0) { final float stackEndHeight = Math.max(0f, @@ -1165,7 +1201,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - void setOnStackYChanged(Runnable onStackYChanged) { + /** + * Add a listener when the StackY changes. The argument signifies whether an animation is + * needed. + */ + void setOnStackYChanged(Consumer<Boolean> onStackYChanged) { mOnStackYChanged = onStackYChanged; } @@ -1600,7 +1640,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); Resources res = getResources(); - mShouldUseSplitNotificationShade = shouldUseSplitNotificationShade(mFeatureFlags, res); + updateSplitNotificationShade(); mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height); float densityScale = res.getDisplayMetrics().density; mSwipeHelper.setDensityScale(densityScale); @@ -2527,8 +2567,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateScrollStateForRemovedChild(child); boolean animationGenerated = generateRemoveAnimation(child); if (animationGenerated) { - if (!mSwipedOutViews.contains(child) - || Math.abs(child.getTranslation()) != child.getWidth()) { + if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) { container.addTransientView(child, 0); child.setTransientContainer(container); } @@ -2540,6 +2579,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable focusNextViewIfFocused(child); } + /** + * Has this view been fully swiped out such that it's not visible anymore. + */ + public boolean isFullySwipedOut(ExpandableView child) { + return Math.abs(child.getTranslation()) >= Math.abs(getTotalTranslationLength(child)); + } + @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void focusNextViewIfFocused(View view) { if (view instanceof ExpandableNotificationRow) { @@ -2659,17 +2705,27 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable final int startingPosition = getPositionInLinearLayout(removedChild); final int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements; final int endPosition = startingPosition + childHeight; - if (endPosition <= mOwnScrollY) { + final int scrollBoundaryStart = getScrollAmountToScrollBoundary(); + mAnimateStackYForContentHeightChange = true; + // This is reset onLayout + if (endPosition <= mOwnScrollY - scrollBoundaryStart) { // This child is fully scrolled of the top, so we have to deduct its height from the // scrollPosition setOwnScrollY(mOwnScrollY - childHeight); - } else if (startingPosition < mOwnScrollY) { + } else if (startingPosition < mOwnScrollY - scrollBoundaryStart) { // This child is currently being scrolled into, set the scroll position to the // start of this child - setOwnScrollY(startingPosition); + setOwnScrollY(startingPosition + scrollBoundaryStart); } } + /** + * @return the amount of scrolling needed to start clipping notifications. + */ + private int getScrollAmountToScrollBoundary() { + return mTopPadding - mQsScrollBoundaryPosition; + } + @ShadeViewRefactor(RefactorComponent.COORDINATOR) private int getIntrinsicHeight(View view) { if (view instanceof ExpandableView) { @@ -2758,7 +2814,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateAnimationState(child); updateChronometerForChild(child); if (child instanceof ExpandableNotificationRow) { - ((ExpandableNotificationRow) child).setDismissRtl(mDismissRtl); + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + row.setDismissRtl(mDismissRtl); + row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX); + } } @@ -2818,6 +2877,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void applyExpandAnimationParams(ExpandAnimationParameters params) { mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange()); + + // Disable clipping for launches + setLaunchingNotification(params != null); requestChildrenUpdate(); } @@ -2901,7 +2963,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAnimationEvents.clear(); updateBackground(); updateViewShadows(); - updateClippingToTopRoundedCorner(); } else { applyCurrentState(); } @@ -3030,7 +3091,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable removedTranslation = row.getTranslationWhenRemoved(); ignoreChildren = false; } - childWasSwipedOut |= Math.abs(row.getTranslation()) == row.getWidth(); + childWasSwipedOut |= isFullySwipedOut(row); } else if (child instanceof MediaHeaderView) { childWasSwipedOut = true; } @@ -3038,11 +3099,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable Rect clipBounds = child.getClipBounds(); childWasSwipedOut = clipBounds != null && clipBounds.height() == 0; - if (childWasSwipedOut && child instanceof ExpandableView) { + if (childWasSwipedOut) { // Clean up any potential transient views if the child has already been swiped // out, as we won't be animating it further (due to its height already being // clipped to 0. - ViewGroup transientContainer = ((ExpandableView) child).getTransientContainer(); + ViewGroup transientContainer = child.getTransientContainer(); if (transientContainer != null) { transientContainer.removeTransientView(child); } @@ -3795,6 +3856,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateNotificationAnimationStates(); updateChronometers(); requestChildrenUpdate(); + updateUseRoundedRectClipping(); } } @@ -3815,6 +3877,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } void onChildHeightChanged(ExpandableView view, boolean needsAnimation) { + boolean previouslyNeededAnimation = mAnimateStackYForContentHeightChange; + if (needsAnimation) { + mAnimateStackYForContentHeightChange = true; + } updateContentHeight(); updateScrollPositionOnExpandInBottom(view); clampScrollPosition(); @@ -3835,6 +3901,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable requestAnimationOnViewResize(row); } requestChildrenUpdate(); + mAnimateStackYForContentHeightChange = previouslyNeededAnimation; } void onChildHeightReset(ExpandableView view) { @@ -4015,7 +4082,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable setAnimationRunning(false); updateBackground(); updateViewShadows(); - updateClippingToTopRoundedCorner(); } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -4048,7 +4114,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable expandableView.setFakeShadowIntensity( diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD, previous.getOutlineAlpha(), (int) yLocation, - previous.getOutlineTranslation()); + (int) (previous.getOutlineTranslation() + previous.getTranslation())); } previous = expandableView; } @@ -4550,6 +4616,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setQsExpansionFraction(float qsExpansionFraction) { mQsExpansionFraction = qsExpansionFraction; + updateUseRoundedRectClipping(); // If notifications are scrolled, // clear out scrollY by the time we push notifications offscreen @@ -4560,13 +4627,18 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void setOwnScrollY(int ownScrollY) { + setOwnScrollY(ownScrollY, false /* animateScrollChangeListener */); + } + + @ShadeViewRefactor(RefactorComponent.COORDINATOR) + private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) { if (ownScrollY != mOwnScrollY) { // We still want to call the normal scrolled changed for accessibility reasons onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY); mOwnScrollY = ownScrollY; mAmbientState.setScrollY(mOwnScrollY); updateOnScrollChange(); - updateStackPosition(); + updateStackPosition(animateStackYChangeListener); } } @@ -4632,6 +4704,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mStatusBarState = statusBarState; mAmbientState.setStatusBarState(statusBarState); updateSpeedBumpIndex(); + updateDismissBehavior(); } void onStatePostChange(boolean fromShadeLocked) { @@ -5192,6 +5265,108 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } /** + * Set rounded rect clipping bounds on this view. + */ + public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius, + int bottomRadius) { + if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right + && mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top + && mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) { + return; + } + mRoundedRectClippingLeft = left; + mRoundedRectClippingTop = top; + mRoundedRectClippingBottom = bottom; + mRoundedRectClippingRight = right; + mBgCornerRadii[0] = topRadius; + mBgCornerRadii[1] = topRadius; + mBgCornerRadii[2] = topRadius; + mBgCornerRadii[3] = topRadius; + mBgCornerRadii[4] = bottomRadius; + mBgCornerRadii[5] = bottomRadius; + mBgCornerRadii[6] = bottomRadius; + mBgCornerRadii[7] = bottomRadius; + mRoundedClipPath.reset(); + mRoundedClipPath.addRoundRect(left, top, right, bottom, mBgCornerRadii, Path.Direction.CW); + if (mShouldUseRoundedRectClipping) { + invalidate(); + } + } + + private void updateSplitNotificationShade() { + boolean split = shouldUseSplitNotificationShade(mFeatureFlags, getResources()); + if (split != mShouldUseSplitNotificationShade) { + mShouldUseSplitNotificationShade = split; + updateDismissBehavior(); + updateUseRoundedRectClipping(); + } + } + + private void updateDismissBehavior() { + // On the split keyguard, dismissing with clipping without a visual boundary looks odd, + // so let's use the content dismiss behavior instead. + boolean dismissUsingRowTranslationX = !mShouldUseSplitNotificationShade + || mStatusBarState != StatusBarState.KEYGUARD; + if (mDismissUsingRowTranslationX != dismissUsingRowTranslationX) { + mDismissUsingRowTranslationX = dismissUsingRowTranslationX; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) child).setDismissUsingRowTranslationX( + dismissUsingRowTranslationX); + } + } + } + } + + /** + * Set if we're launching a notification right now. + */ + private void setLaunchingNotification(boolean launching) { + if (launching == mLaunchingNotification) { + return; + } + mLaunchingNotification = launching; + updateUseRoundedRectClipping(); + } + + /** + * Should we use rounded rect clipping + */ + private void updateUseRoundedRectClipping() { + // We don't want to clip notifications when QS is expanded, because incoming heads up on + // the bottom would be clipped otherwise + boolean qsAllowsClipping = mQsExpansionFraction < 0.5f || mShouldUseSplitNotificationShade; + boolean clip = !mLaunchingNotification && mIsExpanded && qsAllowsClipping; + if (clip != mShouldUseRoundedRectClipping) { + mShouldUseRoundedRectClipping = clip; + invalidate(); + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + if (mShouldUseRoundedRectClipping) { + // Let's clip rounded. + canvas.clipPath(mRoundedClipPath); + } + super.dispatchDraw(canvas); + } + + /** + * Calculate the total translation needed when dismissing. + */ + public float getTotalTranslationLength(View animView) { + if (!mDismissUsingRowTranslationX) { + return animView.getMeasuredWidth(); + } + float notificationWidth = animView.getMeasuredWidth(); + int containerWidth = getMeasuredWidth(); + float padding = (containerWidth - notificationWidth) / 2.0f; + return containerWidth - padding; + } + + /** * A listener that is notified when the empty space below the notifications is clicked on */ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 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 dec98887577e..fb4f5592e97f 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 @@ -384,6 +384,11 @@ public class NotificationStackScrollLayoutController { } @Override + public float getTotalTranslationLength(View animView) { + return mView.getTotalTranslationLength(animView); + } + + @Override public void onSnooze(StatusBarNotification sbn, NotificationSwipeActionHelper.SnoozeOption snoozeOption) { mStatusBar.setNotificationSnoozed(sbn, snoozeOption); @@ -822,8 +827,18 @@ public class NotificationStackScrollLayoutController { return mView.isLayoutRtl(); } + /** + * @return the left of the view. + */ public int getLeft() { - return mView.getLeft(); + return mView.getLeft(); + } + + /** + * @return the top of the view. + */ + public int getTop() { + return mView.getTop(); } public float getTranslationX() { @@ -1008,7 +1023,7 @@ public class NotificationStackScrollLayoutController { mView.setQsExpansionFraction(expansionFraction); } - public void setOnStackYChanged(Runnable onStackYChanged) { + public void setOnStackYChanged(Consumer<Boolean> onStackYChanged) { mView.setOnStackYChanged(onStackYChanged); } @@ -1440,6 +1455,14 @@ public class NotificationStackScrollLayoutController { } /** + * Set rounded rect clipping bounds on this view. + */ + public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius, + int bottomRadius) { + mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius); + } + + /** * Enum for UiEvent logged from this class */ enum NotificationPanelEvent implements UiEventLogger.UiEventEnum { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index f4c4d440b063..664776975b24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -325,6 +325,11 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc } @Override + protected float getTotalTranslationLength(View animView) { + return mCallback.getTotalTranslationLength(animView); + } + + @Override public void setTranslation(View v, float translate) { if (v instanceof SwipeableView) { ((SwipeableView) v).setTranslation(translate); @@ -466,6 +471,13 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc void onSnooze(StatusBarNotification sbn, SnoozeOption snoozeOption); void onDismiss(); + + /** + * Get the total translation length where we want to swipe to when dismissing the view. By + * default this is the size of the view, but can also be larger. + * @param animView the view to ask about + */ + float getTotalTranslationLength(View animView); } static class Builder { 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 e5fd103c239f..74e8de4c9d11 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 @@ -158,7 +158,7 @@ public class StackScrollAlgorithm { AmbientState ambientState) { float drawStart = ambientState.isOnKeyguard() ? 0 : ambientState.getStackY() - ambientState.getScrollY(); - float clipStart = ambientState.getNotificationScrimTop(); + float clipStart = 0; int childCount = algorithmState.visibleChildren.size(); boolean firstHeadsUp = true; for (int i = 0; i < childCount; i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 4fd2064b394d..ee12b4b2d728 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -392,7 +392,7 @@ public class StackStateAnimator { 0, () -> removeTransientView(changingView), null); } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { - if (Math.abs(changingView.getTranslation()) == changingView.getWidth() + if (mHostLayout.isFullySwipedOut(changingView) && changingView.getTransientContainer() != null) { changingView.getTransientContainer().removeTransientView(changingView); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 01d489f91de2..c4d1abc1b74c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -63,6 +63,7 @@ public class DozeParameters implements TunerService.Tunable, private final Resources mResources; private final BatteryController mBatteryController; private final FeatureFlags mFeatureFlags; + private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private final Set<Callback> mCallbacks = new HashSet<>(); @@ -78,7 +79,8 @@ public class DozeParameters implements TunerService.Tunable, BatteryController batteryController, TunerService tunerService, DumpManager dumpManager, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { mResources = resources; mAmbientDisplayConfiguration = ambientDisplayConfiguration; mAlwaysOnPolicy = alwaysOnDisplayPolicy; @@ -89,6 +91,7 @@ public class DozeParameters implements TunerService.Tunable, mPowerManager = powerManager; mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation); mFeatureFlags = featureFlags; + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; tunerService.addTunable( this, @@ -220,7 +223,8 @@ public class DozeParameters implements TunerService.Tunable, * then abruptly showing AOD. */ public boolean shouldControlUnlockedScreenOff() { - return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations(); + return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations() + && mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation(); } private boolean getBoolean(String propName, int resId) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java index 68e20705fbeb..96276f46d23d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java @@ -38,7 +38,7 @@ import java.util.LinkedList; * A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open"). */ public class KeyguardIndicationTextView extends TextView { - private static final long MSG_DURATION_MILLIS = 600; + private static final long MSG_DURATION_MILLIS = 1500; private long mNextAnimationTime = 0; private boolean mAnimationsEnabled = true; private LinkedList<CharSequence> mMessages = new LinkedList<>(); 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 528827f639dd..aaddfca4b685 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -830,6 +830,7 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScrollLayoutController.setOverscrollTopChangedListener( mOnOverscrollTopChangedListener); mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled); + mNotificationStackScrollLayoutController.setOnStackYChanged(this::onStackYChanged); mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener( mOnEmptySpaceClickListener); addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp); @@ -2198,15 +2199,17 @@ public class NotificationPanelViewController extends PanelViewController { mDepthController.setQsPanelExpansion(qsExpansionFraction); } - private Runnable mOnStackYChanged = () -> { + private void onStackYChanged(boolean shouldAnimate) { if (mQs != null) { + if (shouldAnimate) { + mAnimateNextNotificationBounds = true; + mNotificationBoundsAnimationDelay = 0; + } setQSClippingBounds(); } }; private void onNotificationScrolled(int newScrollPosition) { - // Since this is an overscroller, sometimes the scrollY can be temporarily negative - // (when overscrollng on the top and flinging). Let's updateQSExpansionEnabledAmbient(); } @@ -2228,14 +2231,13 @@ public class NotificationPanelViewController extends PanelViewController { * and QS state. */ private void setQSClippingBounds() { - int top = 0; - int bottom = 0; - int left = 0; - int right = 0; + int top; + int bottom; + int left; + int right; final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction()); - final boolean visible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0) - && !mShouldUseSplitNotificationShade; + final boolean qsVisible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0); if (!mShouldUseSplitNotificationShade) { if (mTransitioningToFullShadeProgress > 0.0f) { @@ -2244,7 +2246,6 @@ public class NotificationPanelViewController extends PanelViewController { top = mTransitionToFullShadeQSPosition; } else { final float notificationTop = getQSEdgePosition(); - mAmbientState.setNotificationScrimTop(notificationTop); top = (int) (isOnKeyguard() ? Math.min(qsPanelBottomY, notificationTop) : notificationTop); } @@ -2252,8 +2253,7 @@ public class NotificationPanelViewController extends PanelViewController { // notification bounds should take full screen width regardless of insets left = 0; right = getView().getRight() + mDisplayRightInset; - } else if (qsPanelBottomY > 0) { // so bounds are empty on lockscreen - mAmbientState.setNotificationScrimTop(mSplitShadeNotificationsTopPadding); + } else { top = Math.min(qsPanelBottomY, mSplitShadeNotificationsTopPadding); bottom = mNotificationStackScrollLayoutController.getHeight(); left = mNotificationStackScrollLayoutController.getLeft(); @@ -2261,17 +2261,17 @@ public class NotificationPanelViewController extends PanelViewController { } // top should never be lower than bottom, otherwise it will be invisible. top = Math.min(top, bottom); - applyQSClippingBounds(left, top, right, bottom, visible); + applyQSClippingBounds(left, top, right, bottom, qsVisible); } private void applyQSClippingBounds(int left, int top, int right, int bottom, - boolean visible) { + boolean qsVisible) { if (!mAnimateNextNotificationBounds || mKeyguardStatusAreaClipBounds.isEmpty()) { if (mQsClippingAnimation != null) { // update the end position of the animator mQsClippingAnimationEndBounds.set(left, top, right, bottom); } else { - applyQSClippingImmediately(left, top, right, bottom, visible); + applyQSClippingImmediately(left, top, right, bottom, qsVisible); } } else { mQsClippingAnimationEndBounds.set(left, top, right, bottom); @@ -2295,7 +2295,7 @@ public class NotificationPanelViewController extends PanelViewController { int animBottom = (int) MathUtils.lerp(startBottom, mQsClippingAnimationEndBounds.bottom, fraction); applyQSClippingImmediately(animLeft, animTop, animRight, animBottom, - visible /* visible */); + qsVisible /* qsVisible */); }); mQsClippingAnimation.addListener(new AnimatorListenerAdapter() { @Override @@ -2310,7 +2310,7 @@ public class NotificationPanelViewController extends PanelViewController { } private void applyQSClippingImmediately(int left, int top, int right, int bottom, - boolean visible) { + boolean qsVisible) { // Fancy clipping for quick settings int radius = mScrimCornerRadius; int statusBarClipTop = 0; @@ -2318,19 +2318,34 @@ public class NotificationPanelViewController extends PanelViewController { if (!mShouldUseSplitNotificationShade) { // The padding on this area is large enough that we can use a cheaper clipping strategy mKeyguardStatusAreaClipBounds.set(left, top, right, bottom); - clipStatusView = visible; + clipStatusView = qsVisible; radius = (int) MathUtils.lerp(mScreenCornerRadius, mScrimCornerRadius, Math.min(top / (float) mScrimCornerRadius, 1f)); statusBarClipTop = top - mKeyguardStatusBar.getTop(); } if (mQs != null) { - mQs.setFancyClipping(top, bottom, radius, visible); + mQs.setFancyClipping(top, bottom, radius, qsVisible + && !mShouldUseSplitNotificationShade); } mKeyguardStatusViewController.setClipBounds( clipStatusView ? mKeyguardStatusAreaClipBounds : null); - mScrimController.setNotificationsBounds(left, top, right, bottom); + if (!qsVisible && mShouldUseSplitNotificationShade) { + // On the lockscreen when qs isn't visible, we don't want the bounds of the shade to + // be visible, otherwise you can see the bounds once swiping up to see bouncer + mScrimController.setNotificationsBounds(0, 0, 0, 0); + } else { + mScrimController.setNotificationsBounds(left, top, right, bottom); + } + mScrimController.setScrimCornerRadius(radius); mKeyguardStatusBar.setTopClipping(statusBarClipTop); + int nsslLeft = left - mNotificationStackScrollLayoutController.getLeft(); + int nsslRight = right - mNotificationStackScrollLayoutController.getLeft(); + int nsslTop = top - mNotificationStackScrollLayoutController.getTop(); + int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop(); + int bottomRadius = mShouldUseSplitNotificationShade ? radius : 0; + mNotificationStackScrollLayoutController.setRoundedClippingBounds( + nsslLeft, nsslTop, nsslRight, nsslBottom, radius, bottomRadius); } private float getQSEdgePosition() { @@ -3323,7 +3338,6 @@ public class NotificationPanelViewController extends PanelViewController { // The expandedHeight is always the full panel Height when bypassing expandedHeight = getMaxPanelHeightNonBypass(); } - mNotificationStackScrollLayoutController.setOnStackYChanged(mOnStackYChanged); mNotificationStackScrollLayoutController.setExpandedHeight(expandedHeight); updateKeyguardBottomAreaAlpha(); updateBigClockAlpha(); @@ -4222,7 +4236,7 @@ public class NotificationPanelViewController extends PanelViewController { int oldState = mBarState; boolean keyguardShowing = statusBarState == KEYGUARD; - if (mUnlockedScreenOffAnimationController.shouldPlayScreenOffAnimation() + if (mDozeParameters.shouldControlUnlockedScreenOff() && oldState == StatusBarState.SHADE && statusBarState == KEYGUARD) { // This means we're doing the screen off animation - position the keyguard status diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index 52f9aca82783..c95879650049 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -26,11 +26,9 @@ import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENAB import android.app.IActivityManager; import android.content.Context; import android.content.pm.ActivityInfo; -import android.content.res.Resources; import android.graphics.PixelFormat; import android.os.Binder; import android.os.RemoteException; -import android.os.SystemProperties; import android.os.Trace; import android.util.Log; import android.view.Display; @@ -53,6 +51,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.google.android.collect.Lists; @@ -108,12 +107,14 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW StatusBarStateController statusBarStateController, ConfigurationController configurationController, KeyguardViewMediator keyguardViewMediator, - KeyguardBypassController keyguardBypassController, SysuiColorExtractor colorExtractor, - DumpManager dumpManager) { + KeyguardBypassController keyguardBypassController, + SysuiColorExtractor colorExtractor, + DumpManager dumpManager, + KeyguardStateController keyguardStateController) { mContext = context; mWindowManager = windowManager; mActivityManager = activityManager; - mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation(); + mKeyguardScreenRotation = keyguardStateController.isKeyguardScreenRotationAllowed(); mDozeParameters = dozeParameters; mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze(); mLpChanged = new LayoutParams(); @@ -173,12 +174,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } } - private boolean shouldEnableKeyguardScreenRotation() { - Resources res = mContext.getResources(); - return SystemProperties.getBoolean("lockscreen.rot_override", false) - || res.getBoolean(R.bool.config_enableLockScreenRotation); - } - /** * Adds the notification shade view to the window manager. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 5ee5e489479d..5d2fe523c803 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -21,7 +21,6 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowType; import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; -import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.InsetsState.containsType; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; @@ -46,8 +45,6 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING; import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import static com.android.wm.shell.bubbles.BubbleController.TASKBAR_CHANGED_BROADCAST; -import android.animation.ValueAnimator; -import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; @@ -121,6 +118,7 @@ import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.widget.DateTimeView; +import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; @@ -402,8 +400,6 @@ public class StatusBar extends SystemUI implements DemoMode, private LightRevealScrim mLightRevealScrim; private WiredChargingRippleController mChargingRippleAnimationController; private PowerButtonReveal mPowerButtonReveal; - private CircleReveal mCircleReveal; - private ValueAnimator mCircleRevealAnimator = ValueAnimator.ofFloat(0f, 1f); private final Object mQueueLock = new Object(); @@ -2778,9 +2774,14 @@ public class StatusBar extends SystemUI implements DemoMode, + String.valueOf(CameraIntents.getOverrideCameraPackage(mContext))); } - public static void dumpBarTransitions(PrintWriter pw, String var, BarTransitions transitions) { + public static void dumpBarTransitions( + PrintWriter pw, String var, @Nullable BarTransitions transitions) { pw.print(" "); pw.print(var); pw.print(".BarTransitions.mMode="); - pw.println(BarTransitions.modeToString(transitions.getMode())); + if (transitions != null) { + pw.println(BarTransitions.modeToString(transitions.getMode())); + } else { + pw.println("Unknown"); + } } public void createAndAddWindows(@Nullable RegisterStatusBarResult result) { @@ -2803,11 +2804,11 @@ public class StatusBar extends SystemUI implements DemoMode, return mDisplayMetrics.density; } - float getDisplayWidth() { + public float getDisplayWidth() { return mDisplayMetrics.widthPixels; } - float getDisplayHeight() { + public float getDisplayHeight() { return mDisplayMetrics.heightPixels; } @@ -2841,9 +2842,11 @@ public class StatusBar extends SystemUI implements DemoMode, mActivityIntentHelper.wouldLaunchResolverActivity(intent, mLockscreenUserManager.getCurrentUserId()); + boolean animate = + animationController != null && !willLaunchResolverActivity && shouldAnimateLaunch( + true /* isActivityIntent */); ActivityLaunchAnimator.Controller animController = - !willLaunchResolverActivity && shouldAnimateLaunch(true /* isActivityIntent */) - ? wrapAnimationController(animationController, dismissShade) : null; + animate ? 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. @@ -2857,7 +2860,7 @@ public class StatusBar extends SystemUI implements DemoMode, int[] result = new int[]{ActivityManager.START_CANCELED}; mActivityLaunchAnimator.startIntentWithAnimation(animController, - true /* animate */, intent.getPackage(), (adapter) -> { + animate, intent.getPackage(), (adapter) -> { ActivityOptions options = new ActivityOptions( getActivityOptions(mDisplayId, adapter)); options.setDisallowEnterPictureInPictureWhileLaunching( @@ -2907,16 +2910,12 @@ public class StatusBar extends SystemUI implements DemoMode, } }; executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShadeDirectly, - willLaunchResolverActivity, true /* deferred */); + willLaunchResolverActivity, true /* deferred */, animate); } @Nullable private ActivityLaunchAnimator.Controller wrapAnimationController( - @Nullable ActivityLaunchAnimator.Controller animationController, boolean dismissShade) { - if (animationController == null) { - return null; - } - + ActivityLaunchAnimator.Controller animationController, boolean dismissShade) { View rootView = animationController.getLaunchContainer().getRootView(); if (rootView == mSuperStatusBarViewFactory.getStatusBarWindowView()) { // We are animating a view in the status bar. We have to make sure that the status bar @@ -2959,34 +2958,56 @@ public class StatusBar extends SystemUI implements DemoMode, final boolean dismissShade, final boolean afterKeyguardGone, final boolean deferred) { - dismissKeyguardThenExecute(() -> { - if (runnable != null) { - if (mStatusBarKeyguardViewManager.isShowing() - && mStatusBarKeyguardViewManager.isOccluded()) { - mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); - } else { - AsyncTask.execute(runnable); - } - } - if (dismissShade) { - if (mExpandedVisible && !mBouncerShowing) { - mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */, true /* delayed*/); - } else { + executeRunnableDismissingKeyguard(runnable, cancelAction, dismissShade, afterKeyguardGone, + deferred, false /* willAnimateOnKeyguard */); + } - // Do it after DismissAction has been processed to conserve the needed ordering. - mHandler.post(mShadeController::runPostCollapseRunnables); + 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 { + AsyncTask.execute(runnable); + } } - } else if (isInLaunchTransition() - && mNotificationPanelViewController.isLaunchTransitionFinished()) { + 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. + mHandler.post(mShadeController::runPostCollapseRunnables); + } + } else if (StatusBar.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. + mHandler.post(mStatusBarKeyguardViewManager::readyForKeyguardDone); + } + return deferred; + } - // 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. - mHandler.post(mStatusBarKeyguardViewManager::readyForKeyguardDone); + @Override + public boolean willRunAnimationOnKeyguard() { + return willAnimateOnKeyguard; } - return deferred; - }, cancelAction, afterKeyguardGone); + }; + dismissKeyguardThenExecute(onDismissAction, cancelAction, afterKeyguardGone); } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -3408,6 +3429,10 @@ public class StatusBar extends SystemUI implements DemoMode, } boolean updateIsKeyguard() { + return updateIsKeyguard(false /* force */); + } + + boolean updateIsKeyguard(boolean force) { boolean wakeAndUnlocking = mBiometricUnlockController.getMode() == BiometricUnlockController.MODE_WAKE_AND_UNLOCK; @@ -3431,7 +3456,7 @@ public class StatusBar extends SystemUI implements DemoMode, showKeyguardImpl(); } } else { - return hideKeyguardImpl(); + return hideKeyguardImpl(force); } return false; } @@ -3513,9 +3538,6 @@ public class StatusBar extends SystemUI implements DemoMode, public void fadeKeyguardWhilePulsing() { mNotificationPanelViewController.fadeOut(0, FADE_KEYGUARD_DURATION_PULSING, ()-> { - if (shouldShowCircleReveal()) { - startCircleReveal(); - } hideKeyguard(); mStatusBarKeyguardViewManager.onKeyguardFadedAway(); }).start(); @@ -3560,11 +3582,11 @@ public class StatusBar extends SystemUI implements DemoMode, /** * @return true if we would like to stay in the shade, false if it should go away entirely */ - public boolean hideKeyguardImpl() { + public boolean hideKeyguardImpl(boolean force) { mIsKeyguard = false; Trace.beginSection("StatusBar#hideKeyguard"); boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide(); - if (!(mStatusBarStateController.setState(StatusBarState.SHADE))) { + if (!(mStatusBarStateController.setState(StatusBarState.SHADE, force))) { //TODO: StatusBarStateController should probably know about hiding the keyguard and // notify listeners. @@ -3856,7 +3878,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onDozeAmountChanged(float linear, float eased) { if (mFeatureFlags.useNewLockscreenAnimations() - && !mCircleRevealAnimator.isRunning()) { + && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { mLightRevealScrim.setRevealAmount(1f - linear); } } @@ -3879,7 +3901,7 @@ public class StatusBar extends SystemUI implements DemoMode, || (!isDozing && mWakefulnessLifecycle.getLastWakeReason() == PowerManager.WAKE_REASON_POWER_BUTTON)) { mLightRevealScrim.setRevealEffect(mPowerButtonReveal); - } else if (!mCircleRevealAnimator.isRunning()) { + } else if (!(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE); } @@ -3891,36 +3913,8 @@ public class StatusBar extends SystemUI implements DemoMode, Trace.endSection(); } - /** - * Update the parameters for the dozing circle reveal that animates when the user authenticates - * from AOD using the fingerprint sensor. - */ - public void updateCircleReveal() { - final PointF fpLocation = mAuthRippleController.getFingerprintSensorLocation(); - if (fpLocation != null) { - mCircleReveal = - new CircleReveal( - fpLocation.x, - fpLocation.y, - 0, - Math.max(Math.max(fpLocation.x, getDisplayWidth() - fpLocation.x), - Math.max(fpLocation.y, getDisplayHeight() - fpLocation.y))); - } - } - - private void startCircleReveal() { - mLightRevealScrim.setRevealEffect(mCircleReveal); - mCircleRevealAnimator.cancel(); - mCircleRevealAnimator.addUpdateListener(animation -> - mLightRevealScrim.setRevealAmount( - (float) mCircleRevealAnimator.getAnimatedValue())); - mCircleRevealAnimator.setDuration(900); - mCircleRevealAnimator.start(); - } - - private boolean shouldShowCircleReveal() { - return mCircleReveal != null && !mCircleRevealAnimator.isRunning() - && mBiometricUnlockController.getBiometricType() == FINGERPRINT; + public LightRevealScrim getLightRevealScrim() { + return mLightRevealScrim; } private void updateKeyguardState() { @@ -4060,7 +4054,7 @@ public class StatusBar extends SystemUI implements DemoMode, // The screen off animation uses our LightRevealScrim - we need to be expanded for it to // be visible. - if (mUnlockedScreenOffAnimationController.shouldPlayScreenOffAnimation()) { + if (mDozeParameters.shouldControlUnlockedScreenOff()) { makeExpandedVisible(true); } @@ -4609,28 +4603,37 @@ public class StatusBar extends SystemUI implements DemoMode, * * @param action The action to execute after dismissing the keyguard. * @param collapsePanel Whether we should collapse the panel after dismissing the keyguard. - * @param deferKeyguardDismiss Whether we should defer the keyguard actual dismissal, for - * instance to run animations on the keyguard before hiding it. + * @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 deferKeyguardDismiss) { + boolean collapsePanel, boolean willAnimateOnKeyguard) { if (!mDeviceProvisionedController.isDeviceProvisioned()) return; - dismissKeyguardThenExecute(() -> { - 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(); + 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() : deferKeyguardDismiss; - }, afterKeyguardGone); + return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard; + } + + @Override + public boolean willRunAnimationOnKeyguard() { + return willAnimateOnKeyguard; + } + }; + dismissKeyguardThenExecute(onDismissAction, afterKeyguardGone); } @Override @@ -4674,7 +4677,6 @@ public class StatusBar extends SystemUI implements DemoMode, // 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; - boolean deferKeyguardDismiss = animate; executeActionDismissingKeyguard(() -> { try { // We wrap animationCallback with a StatusBarLaunchAnimatorController so that the @@ -4703,7 +4705,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (intentSentUiThreadCallback != null) { postOnUiThread(intentSentUiThreadCallback); } - }, willLaunchResolverActivity, collapse, deferKeyguardDismiss); + }, willLaunchResolverActivity, collapse, animate); } private void postOnUiThread(Runnable runnable) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index c7efcb2923e7..e8463992ed13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -192,6 +192,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private OnDismissAction mAfterKeyguardGoneAction; private Runnable mKeyguardGoneCancelAction; + private boolean mDismissActionWillAnimateOnKeyguard; private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>(); // Dismiss action to be launched when we stop dozing or the keyguard is gone. @@ -447,6 +448,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mAfterKeyguardGoneAction = r; mKeyguardGoneCancelAction = cancelAction; + mDismissActionWillAnimateOnKeyguard = r != null && r.willRunAnimationOnKeyguard(); // If there is an an alternate auth interceptor (like the UDFPS), show that one instead // of the bouncer. @@ -625,9 +627,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBouncer.startPreHideAnimation(finishRunnable); mStatusBar.onBouncerPreHideAnimation(); - // startPreHideAnimation() will change the visibility of the bouncer, so we have to - // make sure to update its state. - updateStates(); + // We update the state (which will show the keyguard) only if an animation will run on + // the keyguard. If there is no animation, we wait before updating the state so that we + // go directly from bouncer to launcher/app. + if (mDismissActionWillAnimateOnKeyguard) { + updateStates(); + } } else if (finishRunnable != null) { finishRunnable.run(); } @@ -798,6 +803,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mAfterKeyguardGoneAction = null; } mKeyguardGoneCancelAction = null; + mDismissActionWillAnimateOnKeyguard = false; for (int i = 0; i < mAfterKeyguardGoneRunnables.size(); i++) { mAfterKeyguardGoneRunnables.get(i).run(); } @@ -866,6 +872,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb return; // allow bouncer to trigger saved actions } mAfterKeyguardGoneAction = null; + mDismissActionWillAnimateOnKeyguard = false; if (mKeyguardGoneCancelAction != null) { mKeyguardGoneCancelAction.run(); mKeyguardGoneCancelAction = null; @@ -892,6 +899,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb }; protected void updateStates() { + if (mContainer == null ) { + return; + } int vis = mContainer.getSystemUiVisibility(); boolean showing = mShowing; boolean occluded = mOccluded; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index d93b76646d58..98b9cc9bc716 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -41,6 +41,7 @@ import android.text.TextUtils; import android.util.EventLog; import android.view.View; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.widget.LockPatternUtils; @@ -260,10 +261,19 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit boolean showOverLockscreen = mKeyguardStateController.isShowing() && intent != null && mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(), mLockscreenUserManager.getCurrentUserId()); - ActivityStarter.OnDismissAction postKeyguardAction = - () -> handleNotificationClickAfterKeyguardDismissed( + ActivityStarter.OnDismissAction postKeyguardAction = new ActivityStarter.OnDismissAction() { + @Override + public boolean onDismiss() { + return handleNotificationClickAfterKeyguardDismissed( entry, row, controller, intent, isActivityIntent, animate, showOverLockscreen); + } + + @Override + public boolean willRunAnimationOnKeyguard() { + return animate; + } + }; if (showOverLockscreen) { mIsCollapsingToShowActivityOverLockscreen = true; postKeyguardAction.onDismiss(); @@ -453,53 +463,76 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit public void startNotificationGutsIntent(final Intent intent, final int appUid, ExpandableNotificationRow row) { boolean animate = mStatusBar.shouldAnimateLaunch(true /* isActivityIntent */); - mActivityStarter.dismissKeyguardThenExecute(() -> { - AsyncTask.execute(() -> { - ActivityLaunchAnimator.Controller animationController = - new StatusBarLaunchAnimatorController( - mNotificationAnimationProvider.getAnimatorController(row), - mStatusBar, true /* isActivityIntent */); - - mActivityLaunchAnimator.startIntentWithAnimation( - animationController, animate, intent.getPackage(), - (adapter) -> TaskStackBuilder.create(mContext) - .addNextIntentWithParentStack(intent) - .startActivities(getActivityOptions( - mStatusBar.getDisplayId(), - adapter), - new UserHandle(UserHandle.getUserId(appUid)))); - }); - return true; - }, null, false /* afterKeyguardGone */); + ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() { + @Override + public boolean onDismiss() { + AsyncTask.execute(() -> { + ActivityLaunchAnimator.Controller animationController = + new StatusBarLaunchAnimatorController( + mNotificationAnimationProvider.getAnimatorController(row), + mStatusBar, true /* isActivityIntent */); + + mActivityLaunchAnimator.startIntentWithAnimation( + animationController, animate, intent.getPackage(), + (adapter) -> TaskStackBuilder.create(mContext) + .addNextIntentWithParentStack(intent) + .startActivities(getActivityOptions( + mStatusBar.getDisplayId(), + adapter), + new UserHandle(UserHandle.getUserId(appUid)))); + }); + return true; + } + + @Override + public boolean willRunAnimationOnKeyguard() { + return animate; + } + }; + mActivityStarter.dismissKeyguardThenExecute(onDismissAction, null, + false /* afterKeyguardGone */); } @Override public void startHistoryIntent(View view, boolean showHistory) { boolean animate = mStatusBar.shouldAnimateLaunch(true /* isActivityIntent */); - mActivityStarter.dismissKeyguardThenExecute(() -> { - AsyncTask.execute(() -> { - Intent intent = showHistory ? new Intent( - Settings.ACTION_NOTIFICATION_HISTORY) : new Intent( - Settings.ACTION_NOTIFICATION_SETTINGS); - TaskStackBuilder tsb = TaskStackBuilder.create(mContext) - .addNextIntent(new Intent(Settings.ACTION_NOTIFICATION_SETTINGS)); - if (showHistory) { - tsb.addNextIntent(intent); - } + ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() { + @Override + public boolean onDismiss() { + AsyncTask.execute(() -> { + Intent intent = showHistory ? new Intent( + Settings.ACTION_NOTIFICATION_HISTORY) : new Intent( + Settings.ACTION_NOTIFICATION_SETTINGS); + TaskStackBuilder tsb = TaskStackBuilder.create(mContext) + .addNextIntent(new Intent(Settings.ACTION_NOTIFICATION_SETTINGS)); + if (showHistory) { + tsb.addNextIntent(intent); + } - ActivityLaunchAnimator.Controller animationController = - new StatusBarLaunchAnimatorController( - ActivityLaunchAnimator.Controller.fromView(view), mStatusBar, - true /* isActivityIntent */); + ActivityLaunchAnimator.Controller viewController = + ActivityLaunchAnimator.Controller.fromView(view, + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON + ); + ActivityLaunchAnimator.Controller animationController = + new StatusBarLaunchAnimatorController(viewController, mStatusBar, + true /* isActivityIntent */); + + mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate, + intent.getPackage(), + (adapter) -> tsb.startActivities( + getActivityOptions(mStatusBar.getDisplayId(), adapter), + UserHandle.CURRENT)); + }); + return true; + } - mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate, - intent.getPackage(), - (adapter) -> tsb.startActivities( - getActivityOptions(mStatusBar.getDisplayId(), adapter), - UserHandle.CURRENT)); - }); - return true; - }, null, false /* afterKeyguardGone */); + @Override + public boolean willRunAnimationOnKeyguard() { + return animate; + } + }; + mActivityStarter.dismissKeyguardThenExecute(onDismissAction, null, + false /* afterKeyguardGone */); } private void removeHunAfterClick(ExpandableNotificationRow row) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index e135cc51a7bc..52bf2d577776 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -3,6 +3,8 @@ package com.android.systemui.statusbar.phone import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator +import android.content.Context +import android.content.res.Configuration import android.os.Handler import android.view.View import com.android.systemui.animation.Interpolators @@ -16,6 +18,7 @@ import com.android.systemui.statusbar.notification.AnimatableProperty import com.android.systemui.statusbar.notification.PropertyAnimator import com.android.systemui.statusbar.notification.stack.AnimationProperties import com.android.systemui.statusbar.notification.stack.StackStateAnimator +import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject /** @@ -38,10 +41,11 @@ private const val LIGHT_REVEAL_ANIMATION_DURATION = 750L */ @SysUISingleton class UnlockedScreenOffAnimationController @Inject constructor( + private val context: Context, private val wakefulnessLifecycle: WakefulnessLifecycle, private val statusBarStateControllerImpl: StatusBarStateControllerImpl, private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>, - private val dozeParameters: DozeParameters + private val keyguardStateController: KeyguardStateController ) : WakefulnessLifecycle.Observer { private val handler = Handler() @@ -131,13 +135,18 @@ class UnlockedScreenOffAnimationController @Inject constructor( lightRevealAnimationPlaying = false aodUiAnimationPlaying = false - // Make sure the status bar is in the correct keyguard state, since we might have left it in - // the KEYGUARD state if this wakeup cancelled the screen off animation. - statusBar.updateIsKeyguard() + // Make sure the status bar is in the correct keyguard state, forcing it if necessary. This + // is required if the screen off animation is cancelled, since it might be incorrectly left + // in the KEYGUARD or SHADE states depending on when it was cancelled and whether 'lock + // instantly' is enabled. We need to force it so that the state is set even if we're going + // from SHADE to SHADE or KEYGUARD to KEYGUARD, since we might have changed parts of the UI + // (such as showing AOD in the shade) without actually changing the StatusBarState. This + // ensures that the UI definitely reflects the desired state. + statusBar.updateIsKeyguard(true /* force */) } override fun onStartedGoingToSleep() { - if (shouldPlayScreenOffAnimation()) { + if (shouldPlayUnlockedScreenOffAnimation()) { lightRevealAnimationPlaying = true lightRevealAnimator.start() @@ -151,13 +160,31 @@ class UnlockedScreenOffAnimationController @Inject constructor( } /** - * Whether we should play the screen off animation when the phone starts going to sleep. We can - * do that if dozeParameters says we can control the unlocked screen off animation and we are in - * the SHADE state. If we're in KEYGUARD or SHADE_LOCKED, the regular + * Whether we want to play the screen off animation when the phone starts going to sleep, based + * on the current state of the device. */ - fun shouldPlayScreenOffAnimation(): Boolean { - return dozeParameters.shouldControlUnlockedScreenOff() && - statusBarStateControllerImpl.state == StatusBarState.SHADE + fun shouldPlayUnlockedScreenOffAnimation(): Boolean { + // We only play the unlocked screen off animation if we are... unlocked. + if (statusBarStateControllerImpl.state != StatusBarState.SHADE) { + return false + } + + // We currently draw both the light reveal scrim, and the AOD UI, in the shade. If it's + // already expanded and showing notifications/QS, the animation looks really messy. For now, + // disable it if the notification panel is expanded. + if (statusBar.notificationPanelViewController.isFullyExpanded) { + return false + } + + // If we're not allowed to rotate the keyguard, then only do the screen off animation if + // we're in portrait. Otherwise, AOD will animate in sideways, which looks weird. + if (!keyguardStateController.isKeyguardScreenRotationAllowed && + context.resources.configuration.orientation != Configuration.ORIENTATION_PORTRAIT) { + return false + } + + // Otherwise, good to go. + return true } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index ef7fac311799..b295f6659f81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -25,6 +25,7 @@ import android.content.Intent import android.util.Log import android.view.View import android.widget.Chronometer +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.R import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.dagger.SysUISingleton @@ -179,7 +180,10 @@ class OngoingCallController @Inject constructor( logger.logChipClicked() activityStarter.postStartActivityDismissingKeyguard( currentCallNotificationInfo.intent, 0, - ActivityLaunchAnimator.Controller.fromView(backgroundView)) + ActivityLaunchAnimator.Controller.fromView( + backgroundView, + InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP) + ) } setUpUidObserver(currentCallNotificationInfo) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java index af7bf9500bf3..fcfc9670b8b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java @@ -57,6 +57,11 @@ public interface KeyguardStateController extends CallbackController<Callback> { boolean canPerformSmartSpaceTransition(); /** + * Whether the keyguard is allowed to rotate, or needs to be locked to the default orientation. + */ + boolean isKeyguardScreenRotationAllowed(); + + /** * If the device has PIN/pattern/password or a lock screen at all. */ boolean isMethodSecure(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 0945a3f884d6..64750bd803d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.hardware.biometrics.BiometricSourceType; import android.os.Build; +import android.os.SystemProperties; import android.os.Trace; import androidx.annotation.VisibleForTesting; @@ -31,6 +32,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Dumpable; +import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController; @@ -50,6 +52,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum private static final String AUTH_BROADCAST_KEY = "debug_trigger_auth"; private final ArrayList<Callback> mCallbacks = new ArrayList<>(); + private final Context mContext; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final LockPatternUtils mLockPatternUtils; private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = @@ -100,6 +103,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum public KeyguardStateControllerImpl(Context context, KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils, SmartspaceTransitionController smartspaceTransitionController) { + mContext = context; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); @@ -243,6 +247,12 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum } @Override + public boolean isKeyguardScreenRotationAllowed() { + return SystemProperties.getBoolean("lockscreen.rot_override", false) + || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation); + } + + @Override public boolean isFaceAuthEnabled() { return mFaceAuthEnabled; } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java index 26f4a2b9d2b2..d97815f92964 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java @@ -68,7 +68,8 @@ public class TunerServiceImpl extends TunerService { private static final String[] RESET_EXCEPTION_LIST = new String[] { QSTileHost.TILES_SETTING, Settings.Secure.DOZE_ALWAYS_ON, - Settings.Secure.MEDIA_CONTROLS_RESUME + Settings.Secure.MEDIA_CONTROLS_RESUME, + Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION }; private final Observer mObserver = new Observer(); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 92ef8504d123..3af82f91af1c 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -34,6 +34,7 @@ import android.graphics.drawable.Drawable; import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.view.KeyEvent; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; @@ -57,6 +58,7 @@ import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.nano.WmShellTraceProto; import com.android.wm.shell.onehanded.OneHanded; +import com.android.wm.shell.onehanded.OneHandedEventCallback; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.onehanded.OneHandedUiEventLogger; import com.android.wm.shell.pip.Pip; @@ -253,6 +255,15 @@ public final class WMShell extends SystemUI } }); + oneHanded.registerEventCallback(new OneHandedEventCallback() { + @Override + public void notifyExpandNotification() { + mSysUiMainExecutor.execute( + () -> mCommandQueue.handleSystemKey( + KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN)); + } + }); + mOneHandedKeyguardCallback = new KeyguardUpdateMonitorCallback() { @Override public void onKeyguardBouncerChanged(boolean bouncer) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index 240fdf3a4e17..d87a26b096fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -25,6 +25,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.policy.ConfigurationController @@ -53,6 +54,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Mock private lateinit var authController: AuthController @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController @Mock private lateinit var bypassController: KeyguardBypassController + @Mock private lateinit var biometricUnlockController: BiometricUnlockController @Before fun setUp() { @@ -66,6 +68,7 @@ class AuthRippleControllerTest : SysuiTestCase() { commandRegistry, notificationShadeWindowController, bypassController, + biometricUnlockController, rippleView ) controller.init() @@ -90,7 +93,7 @@ class AuthRippleControllerTest : SysuiTestCase() { // THEN update sensor location and show ripple verify(rippleView).setSensorLocation(fpsLocation) - verify(rippleView).startRipple(any()) + verify(rippleView).startRipple(any(), any()) } @Test @@ -111,7 +114,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test @@ -132,7 +135,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test @@ -156,7 +159,7 @@ class AuthRippleControllerTest : SysuiTestCase() { // THEN show ripple verify(rippleView).setSensorLocation(faceLocation) - verify(rippleView).startRipple(any()) + verify(rippleView).startRipple(any(), any()) } @Test @@ -176,7 +179,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test @@ -191,7 +194,7 @@ class AuthRippleControllerTest : SysuiTestCase() { 0 /* userId */, BiometricSourceType.FACE /* type */, false /* isStrongBiometric */) - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test @@ -206,7 +209,7 @@ class AuthRippleControllerTest : SysuiTestCase() { 0 /* userId */, BiometricSourceType.FINGERPRINT /* type */, false /* isStrongBiometric */) - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index c0b45c6d5c96..a11b9cf357a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -16,7 +16,10 @@ package com.android.systemui.doze; +import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD; + import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; @@ -161,7 +164,7 @@ public class DozeTriggersTest extends SysuiTestCase { clearInvocations(mSensors); mTriggers.transitionTo(DozeMachine.State.DOZE_PULSING, DozeMachine.State.DOZE_PULSE_DONE); - mTriggers.transitionTo(DozeMachine.State.DOZE_PULSE_DONE, DozeMachine.State.DOZE_AOD); + mTriggers.transitionTo(DozeMachine.State.DOZE_PULSE_DONE, DOZE_AOD); waitForSensorManager(); verify(mSensors).requestTriggerSensor(any(), eq(mTapSensor)); } @@ -207,7 +210,7 @@ public class DozeTriggersTest extends SysuiTestCase { mTriggers.onSensor(DozeLog.REASON_SENSOR_QUICK_PICKUP, 100, 100, null); // THEN device goes into aod (shows clock with black background) - verify(mMachine).requestState(DozeMachine.State.DOZE_AOD); + verify(mMachine).requestState(DOZE_AOD); // THEN a log is taken that quick pick up was triggered verify(mUiEventLogger).log(DozingUpdateUiEvent.DOZING_UPDATE_QUICK_PICKUP); @@ -218,7 +221,7 @@ public class DozeTriggersTest extends SysuiTestCase { // GIVEN quick pickup is triggered when device is in DOZE when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE); mTriggers.onSensor(DozeLog.REASON_SENSOR_QUICK_PICKUP, 100, 100, null); - verify(mMachine).requestState(DozeMachine.State.DOZE_AOD); + verify(mMachine).requestState(DOZE_AOD); verify(mMachine, never()).requestState(DozeMachine.State.DOZE); // WHEN next executable is run @@ -234,6 +237,8 @@ public class DozeTriggersTest extends SysuiTestCase { @Test public void testOnSensor_Fingerprint() { + // GIVEN dozing state + when(mMachine.getState()).thenReturn(DOZE_AOD); final int screenX = 100; final int screenY = 100; final float misc = -1; @@ -241,8 +246,20 @@ public class DozeTriggersTest extends SysuiTestCase { final float major = 3f; final int reason = DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS; float[] rawValues = new float[]{screenX, screenY, misc, major, minor}; + + // WHEN longpress gesture is triggered mTriggers.onSensor(reason, screenX, screenY, rawValues); + + // THEN + // * don't immediately send interrupt + // * immediately extend pulse + verify(mAuthController, never()).onAodInterrupt(anyInt(), anyInt(), anyFloat(), anyFloat()); verify(mHost).extendPulse(reason); + + // WHEN display state changes to ON + mTriggers.onScreenState(Display.STATE_ON); + + // THEN send interrupt verify(mAuthController).onAodInterrupt(eq(screenX), eq(screenY), eq(major), eq(minor)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java index afe5c0b2edbd..1d34aac3b1cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java @@ -43,6 +43,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.wakelock.WakeLockFake; @@ -77,6 +78,8 @@ public class DozeUiTest extends SysuiTestCase { private DozeUi mDozeUi; @Mock private StatusBarStateController mStatusBarStateController; + @Mock + private ConfigurationController mConfigurationController; @Before public void setUp() throws Exception { @@ -89,7 +92,7 @@ public class DozeUiTest extends SysuiTestCase { mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler, mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService, - () -> mStatusBarStateController); + () -> mStatusBarStateController, mConfigurationController); mDozeUi.setDozeMachine(mMachine); } @@ -146,7 +149,7 @@ public class DozeUiTest extends SysuiTestCase { when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true); mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler, mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService, - () -> mStatusBarStateController); + () -> mStatusBarStateController, mConfigurationController); mDozeUi.setDozeMachine(mMachine); // Never animate if display doesn't support it. diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index 83e7b17eb746..adc8ffc1d633 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.when; import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Color; import android.media.AudioManager; @@ -38,8 +39,9 @@ import android.os.UserManager; import android.service.dreams.IDreamManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.GestureDetector; import android.view.IWindowManager; -import android.view.View; +import android.view.MotionEvent; import android.view.WindowManagerPolicyConstants; import androidx.test.filters.SmallTest; @@ -57,6 +59,7 @@ import com.android.systemui.plugins.GlobalActions; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -107,8 +110,10 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private RingerModeTracker mRingerModeTracker; @Mock private RingerModeLiveData mRingerModeLiveData; @Mock private SysUiState mSysUiState; + @Mock private PackageManager mPackageManager; @Mock private Handler mHandler; @Mock private UserContextProvider mUserContextProvider; + @Mock private StatusBar mStatusBar; private TestableLooper mTestableLooper; @@ -120,6 +125,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); when(mUserContextProvider.getUserContext()).thenReturn(mContext); + when(mResources.getConfiguration()).thenReturn( + getContext().getResources().getConfiguration()); mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext, mWindowManagerFuncs, @@ -150,7 +157,9 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mInfoProvider, mRingerModeTracker, mSysUiState, - mHandler + mHandler, + mPackageManager, + mStatusBar ); mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting(); @@ -194,7 +203,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { } @Test - public void testShouldLogOnTapOutside() { + public void testSingleTap_logAndDismiss() { mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); @@ -207,9 +216,58 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { }; doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); - View container = dialog.findViewById(com.android.systemui.R.id.global_actions_container); - container.callOnClick(); + + GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener); + gestureListener.onSingleTapConfirmed(null); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); + } + + @Test + public void testSwipeDownLockscreen_logAndOpenQS() { + mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); + doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); + doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); + doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); + doReturn(true).when(mStatusBar).isKeyguardShowing(); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); + GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); + + GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener); + MotionEvent start = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); + MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0); + gestureListener.onFling(start, end, 0, 1000); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); + verify(mStatusBar).animateExpandSettingsPanel(null); + } + + @Test + public void testSwipeDown_logAndOpenNotificationShade() { + mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); + doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); + doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); + doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); + doReturn(false).when(mStatusBar).isKeyguardShowing(); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); + GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); + + GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener); + MotionEvent start = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); + MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0); + gestureListener.onFling(start, end, 0, 1000); verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); + verify(mStatusBar).animateExpandNotificationsPanel(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java index 3130e977dc83..e5c104e7d377 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -33,6 +33,7 @@ import static org.mockito.Mockito.when; import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; +import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Color; @@ -65,6 +66,7 @@ import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -123,7 +125,9 @@ public class GlobalActionsDialogTest extends SysuiTestCase { @Mock GlobalActionsPanelPlugin.PanelViewController mWalletController; @Mock private Handler mHandler; @Mock private UserTracker mUserTracker; + @Mock private PackageManager mPackageManager; @Mock private SecureSettings mSecureSettings; + @Mock private StatusBar mStatusBar; private TestableLooper mTestableLooper; @@ -134,6 +138,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase { allowTestableLooperAsMainThread(); when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); + when(mResources.getConfiguration()).thenReturn( + getContext().getResources().getConfiguration()); mGlobalActionsDialog = new GlobalActionsDialog(mContext, mWindowManagerFuncs, @@ -164,7 +170,9 @@ public class GlobalActionsDialogTest extends SysuiTestCase { mUiEventLogger, mRingerModeTracker, mSysUiState, - mHandler + mHandler, + mPackageManager, + mStatusBar ); mGlobalActionsDialog.setZeroDialogPressDelayForTesting(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java index 2f78532b9e71..51576687880c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java @@ -34,7 +34,6 @@ import android.content.res.ColorStateList; import android.graphics.Color; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; -import android.view.View; import androidx.test.filters.SmallTest; @@ -258,8 +257,8 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas // WHEN the device is dozing mStatusBarStateListener.onDozingChanged(true); - // THEN the view is GONE - verify(mView).setVisibility(View.GONE); + // THEN switch to INDICATION_TYPE_NONE + verify(mView).switchIndication(null); } @Test 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 15cfee828293..3128db423a24 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -9,8 +9,8 @@ import android.media.MediaDescription import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.MediaSession -import android.provider.Settings import android.os.Bundle +import android.provider.Settings import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper @@ -20,6 +20,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter 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.capture import com.android.systemui.util.mockito.eq @@ -86,6 +87,8 @@ class MediaDataManagerTest : SysuiTestCase() { lateinit var mediaNotification: StatusBarNotification @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData> private val clock = FakeSystemClock() + @Mock private lateinit var tunerService: TunerService + @Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable> private val originalSmartspaceSetting = Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1) @@ -114,8 +117,11 @@ class MediaDataManagerTest : SysuiTestCase() { smartspaceMediaDataProvider = smartspaceMediaDataProvider, useMediaResumption = true, useQsMediaPlayer = true, - systemClock = clock + systemClock = clock, + tunerService = tunerService ) + verify(tunerService).addTunable(capture(tunableCaptor), + eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)) session = MediaSession(context, "MediaDataManagerTestSession") mediaNotification = SbnBuilder().run { setPkg(PACKAGE_NAME) @@ -364,6 +370,9 @@ class MediaDataManagerTest : SysuiTestCase() { fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() { smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget)) smartspaceMediaDataProvider.onTargetsAvailable(listOf()) + foregroundExecutor.advanceClockToLast() + foregroundExecutor.runAllReady() + verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false)) } @@ -372,6 +381,8 @@ class MediaDataManagerTest : SysuiTestCase() { // WHEN media recommendation setting is off Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0) + tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0") + smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget)) // THEN smartspace signal is ignored @@ -380,6 +391,24 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test + fun testMediaRecommendationDisabled_removesSmartspaceData() { + // GIVEN a media recommendation card is present + smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget)) + verify(listener).onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), + anyBoolean()) + + // WHEN the media recommendation setting is turned off + Settings.Secure.putInt(context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0) + tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0") + + // THEN listeners are notified + foregroundExecutor.advanceClockToLast() + foregroundExecutor.runAllReady() + verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true)) + } + + @Test fun testOnMediaDataChanged_updatesLastActiveTime() { val currentTime = clock.elapsedRealtime() mediaDataManager.onNotificationAdded(KEY, mediaNotification) diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java index 5f4d90b3666f..f6264ffc6a70 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java @@ -49,6 +49,7 @@ import com.android.systemui.wmshell.BubblesManager; import com.android.wm.shell.bubbles.Bubble; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -213,6 +214,7 @@ public class LaunchConversationActivityTest extends SysuiTestCase { verify(mBubblesManager, never()).expandStackAndSelectBubble(any(NotificationEntry.class)); } + @Ignore @Test public void testBubbleWithNoNotifOpensBubble() throws Exception { Bubble bubble = mock(Bubble.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index b0e3e3e936a9..2ae4cbe17ac6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -46,6 +46,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.media.MediaHost; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.dagger.QSFragmentComponent; +import com.android.systemui.qs.external.CustomTileStatePersister; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSFactoryImpl; import com.android.systemui.settings.UserTracker; @@ -132,7 +133,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { () -> mock(AutoTileManager.class), mock(DumpManager.class), mock(BroadcastDispatcher.class), Optional.of(mock(StatusBar.class)), mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class), - mock(SecureSettings.class)); + mock(SecureSettings.class), mock(CustomTileStatePersister.class)); qs.setHost(host); qs.setListening(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 7c73b4c44e90..69bdcbcff270 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -59,6 +59,8 @@ import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.external.CustomTile; +import com.android.systemui.qs.external.CustomTileStatePersister; +import com.android.systemui.qs.external.TileServiceKey; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; @@ -125,6 +127,8 @@ public class QSTileHostTest extends SysuiTestCase { private UserTracker mUserTracker; @Mock private SecureSettings mSecureSettings; + @Mock + private CustomTileStatePersister mCustomTileStatePersister; private Handler mHandler; private TestableLooper mLooper; @@ -145,7 +149,7 @@ public class QSTileHostTest extends SysuiTestCase { mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler, mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager, mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker, - mSecureSettings); + mSecureSettings, mCustomTileStatePersister); setUpTileFactory(); when(mSecureSettings.getStringForUser(eq(QSTileHost.TILES_SETTING), anyInt())) @@ -371,6 +375,14 @@ public class QSTileHostTest extends SysuiTestCase { verify(mQSLogger, never()).logTileDestroyed(isNull(), anyString()); } + @Test + public void testCustomTileRemoved_stateDeleted() { + mQSTileHost.changeTiles(List.of(CUSTOM_TILE_SPEC), List.of()); + + verify(mCustomTileStatePersister) + .removeState(new TileServiceKey(CUSTOM_TILE, mQSTileHost.getUserId())); + } + private class TestQSTileHost extends QSTileHost { TestQSTileHost(Context context, StatusBarIconController iconController, QSFactory defaultFactory, Handler mainHandler, Looper bgLooper, @@ -378,10 +390,11 @@ public class QSTileHostTest extends SysuiTestCase { Provider<AutoTileManager> autoTiles, DumpManager dumpManager, BroadcastDispatcher broadcastDispatcher, StatusBar statusBar, QSLogger qsLogger, UiEventLogger uiEventLogger, UserTracker userTracker, - SecureSettings secureSettings) { + SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister) { super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager, tunerService, autoTiles, dumpManager, broadcastDispatcher, - Optional.of(statusBar), qsLogger, uiEventLogger, userTracker, secureSettings); + Optional.of(statusBar), qsLogger, uiEventLogger, userTracker, secureSettings, + customTileStatePersister); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt new file mode 100644 index 000000000000..6c96576bcbc1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2021 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.qs.external + +import android.content.ComponentName +import android.content.Context +import android.content.SharedPreferences +import android.service.quicksettings.Tile +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Answers +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class CustomTileStatePersisterTest : SysuiTestCase() { + + companion object { + private val TEST_COMPONENT = ComponentName("pkg", "cls") + private const val TEST_USER = 0 + private val KEY = TileServiceKey(TEST_COMPONENT, TEST_USER) + + private const val TEST_STATE = Tile.STATE_INACTIVE + private const val TEST_LABEL = "test_label" + private const val TEST_SUBTITLE = "test_subtitle" + private const val TEST_CONTENT_DESCRIPTION = "test_content_description" + private const val TEST_STATE_DESCRIPTION = "test_state_description" + + private fun Tile.isEqualTo(other: Tile): Boolean { + return state == other.state && + label == other.label && + subtitle == other.subtitle && + contentDescription == other.contentDescription && + stateDescription == other.stateDescription + } + } + + @Mock + private lateinit var mockContext: Context + @Mock + private lateinit var sharedPreferences: SharedPreferences + @Mock(answer = Answers.RETURNS_SELF) + private lateinit var editor: SharedPreferences.Editor + private lateinit var tile: Tile + private lateinit var customTileStatePersister: CustomTileStatePersister + + @Captor + private lateinit var stringCaptor: ArgumentCaptor<String> + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + `when`(mockContext.getSharedPreferences(anyString(), anyInt())) + .thenReturn(sharedPreferences) + `when`(sharedPreferences.edit()).thenReturn(editor) + + tile = Tile() + customTileStatePersister = CustomTileStatePersister(mockContext) + } + + @Test + fun testWriteState() { + tile.apply { + state = TEST_STATE + label = TEST_LABEL + subtitle = TEST_SUBTITLE + contentDescription = TEST_CONTENT_DESCRIPTION + stateDescription = TEST_STATE_DESCRIPTION + } + + customTileStatePersister.persistState(KEY, tile) + + verify(editor).putString(eq(KEY.toString()), capture(stringCaptor)) + + assertThat(tile.isEqualTo(readTileFromString(stringCaptor.value))).isTrue() + } + + @Test + fun testReadState() { + tile.apply { + state = TEST_STATE + label = TEST_LABEL + subtitle = TEST_SUBTITLE + contentDescription = TEST_CONTENT_DESCRIPTION + stateDescription = TEST_STATE_DESCRIPTION + } + + `when`(sharedPreferences.getString(eq(KEY.toString()), any())) + .thenReturn(writeToString(tile)) + + assertThat(tile.isEqualTo(customTileStatePersister.readState(KEY)!!)).isTrue() + } + + @Test + fun testReadStateDefault() { + `when`(sharedPreferences.getString(any(), any())).thenAnswer { + it.getArgument(1) + } + + assertThat(customTileStatePersister.readState(KEY)).isNull() + } + + @Test + fun testStoreNulls() { + assertThat(tile.label).isNull() + + customTileStatePersister.persistState(KEY, tile) + + verify(editor).putString(eq(KEY.toString()), capture(stringCaptor)) + + assertThat(readTileFromString(stringCaptor.value).label).isNull() + } + + @Test + fun testReadNulls() { + assertThat(tile.label).isNull() + + `when`(sharedPreferences.getString(eq(KEY.toString()), any())) + .thenReturn(writeToString(tile)) + + assertThat(customTileStatePersister.readState(KEY)!!.label).isNull() + } + + @Test + fun testRemoveState() { + customTileStatePersister.removeState(KEY) + + verify(editor).remove(KEY.toString()) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt index b1c3d1da8fea..9b5c1619ef31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.util.mockito.any import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -48,8 +49,9 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito.`when` -import org.mockito.Mockito.any import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest @@ -76,6 +78,7 @@ class CustomTileTest : SysuiTestCase() { @Mock private lateinit var packageManager: PackageManager @Mock private lateinit var applicationInfo: ApplicationInfo @Mock private lateinit var serviceInfo: ServiceInfo + @Mock private lateinit var customTileStatePersister: CustomTileStatePersister private lateinit var customTile: CustomTile private lateinit var testableLooper: TestableLooper @@ -108,10 +111,13 @@ class CustomTileTest : SysuiTestCase() { metricsLogger, statusBarStateController, activityStarter, - qsLogger + qsLogger, + customTileStatePersister ) customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + customTile.initialize() + testableLooper.processAllMessages() } @Test @@ -123,6 +129,8 @@ class CustomTileTest : SysuiTestCase() { `when`(userContext.userId).thenReturn(10) val tile = CustomTile.create(customTileBuilder, TILE_SPEC, userContext) + tile.initialize() + testableLooper.processAllMessages() assertEquals(10, tile.user) } @@ -131,6 +139,8 @@ class CustomTileTest : SysuiTestCase() { fun testToggleableTileHasBooleanState() { `when`(tileServiceManager.isToggleableTile).thenReturn(true) customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + customTile.initialize() + testableLooper.processAllMessages() assertTrue(customTile.state is QSTile.BooleanState) assertTrue(customTile.newTileState() is QSTile.BooleanState) @@ -146,6 +156,9 @@ class CustomTileTest : SysuiTestCase() { fun testValueUpdatedInBooleanTile() { `when`(tileServiceManager.isToggleableTile).thenReturn(true) customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + customTile.initialize() + testableLooper.processAllMessages() + customTile.qsTile.icon = mock(Icon::class.java) `when`(customTile.qsTile.icon.loadDrawable(any(Context::class.java))) .thenReturn(mock(Drawable::class.java)) @@ -173,4 +186,88 @@ class CustomTileTest : SysuiTestCase() { .thenReturn(null) customTile.handleUpdateState(customTile.newTileState(), null) } + + @Test + fun testNoLoadStateTileNotActive() { + // Not active by default + testableLooper.processAllMessages() + + verify(customTileStatePersister, never()).readState(any()) + } + + @Test + fun testNoPersistedStateTileNotActive() { + // Not active by default + val t = Tile().apply { + state = Tile.STATE_INACTIVE + } + customTile.updateTileState(t) + testableLooper.processAllMessages() + + verify(customTileStatePersister, never()).persistState(any(), any()) + } + + @Test + fun testPersistedStateRetrieved() { + val state = Tile.STATE_INACTIVE + val label = "test_label" + val subtitle = "test_subtitle" + val contentDescription = "test_content_description" + val stateDescription = "test_state_description" + + val t = Tile().apply { + this.state = state + this.label = label + this.subtitle = subtitle + this.contentDescription = contentDescription + this.stateDescription = stateDescription + } + `when`(tileServiceManager.isActiveTile).thenReturn(true) + `when`(customTileStatePersister + .readState(TileServiceKey(componentName, customTile.user))).thenReturn(t) + val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + tile.initialize() + testableLooper.processAllMessages() + + // Make sure we have an icon in the tile because we don't have a default icon + // This should not be overridden by the retrieved tile that has null icon. + tile.qsTile.icon = mock(Icon::class.java) + `when`(tile.qsTile.icon.loadDrawable(any(Context::class.java))) + .thenReturn(mock(Drawable::class.java)) + + tile.refreshState() + + testableLooper.processAllMessages() + + val tileState = tile.state + + assertEquals(state, tileState.state) + assertEquals(label, tileState.label) + assertEquals(subtitle, tileState.secondaryLabel) + assertEquals(contentDescription, tileState.contentDescription) + assertEquals(stateDescription, tileState.stateDescription) + } + + @Test + fun testStoreStateOnChange() { + val t = Tile().apply { + state = Tile.STATE_INACTIVE + label = "test_label" + subtitle = "test_subtitle" + contentDescription = "test_content_description" + stateDescription = "test_state_description" + } + `when`(tileServiceManager.isActiveTile).thenReturn(true) + + val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + tile.initialize() + testableLooper.processAllMessages() + + tile.updateTileState(t) + + testableLooper.processAllMessages() + + verify(customTileStatePersister) + .persistState(TileServiceKey(componentName, customTile.user), t) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 641f917bcfbe..2b1840462291 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -118,7 +118,8 @@ public class TileServicesTest extends SysuiTestCase { mQSLogger, mUiEventLogger, mUserTracker, - mSecureSettings); + mSecureSettings, + mock(CustomTileStatePersister.class)); mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher, mUserTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java index 80231a49bb44..ea4d7cc2529c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java @@ -121,6 +121,9 @@ public class QSTileImplTest extends SysuiTestCase { mTile = new TileImpl(mHost, mTestableLooper.getLooper(), mainHandler, mFalsingManager, mMetricsLogger, mStatusBarStateController, mActivityStarter, mQsLogger); + mTile.initialize(); + mTestableLooper.processAllMessages(); + mTile.setTileSpec(SPEC); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt index 32b1f433dfcf..5e2d8fde84da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt @@ -80,6 +80,8 @@ class AlarmTileTest : SysuiTestCase() { nextAlarmController ) + tile.initialize() + verify(nextAlarmController).observe(eq(tile), capture(callbackCaptor)) tile.refreshState() testableLooper.processAllMessages() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt index f17bd56d0052..1bf83513d472 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt @@ -87,6 +87,9 @@ class BatterySaverTileTest : SysuiTestCase() { qsLogger, batteryController, secureSettings) + + tile.initialize() + testableLooper.processAllMessages() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java index 7c1a5f5ebf30..d44a52607707 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java @@ -112,11 +112,14 @@ public class CastTileTest extends SysuiTestCase { mNetworkController, mHotspotController ); + mCastTile.initialize(); // We are not setting the mocks to listening, so we trigger a first refresh state to // set the initial state mCastTile.refreshState(); + mTestableLooper.processAllMessages(); + mCastTile.handleSetListening(true); ArgumentCaptor<NetworkController.SignalCallback> signalCallbackArgumentCaptor = ArgumentCaptor.forClass(NetworkController.SignalCallback.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt index 6d1bbd9708ea..94af10a485fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt @@ -378,7 +378,10 @@ class DeviceControlsTileTest : SysuiTestCase() { qsLogger, controlsComponent, keyguardStateController - ) + ).also { + it.initialize() + testableLooper.processAllMessages() + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java index 99d028cd8c5c..cfd37358dcff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java @@ -92,6 +92,9 @@ public class NfcTileTest extends SysuiTestCase { mQSLogger, mBroadcastDispatcher ); + + mNfcTile.initialize(); + mTestableLooper.processAllMessages(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java index e4a9aacb57ab..a50cbe5adc48 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -155,6 +155,9 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { mPackageManager, mSecureSettings, mController); + + mTile.initialize(); + mTestableLooper.processAllMessages(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java index df4908ddc4ef..9eb688de3511 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java @@ -89,6 +89,9 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase { mActivityStarter, mQSLogger ); + + mTile.initialize(); + mTestableLooper.processAllMessages(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java index 3b4e863ed8bd..964ce01312bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java @@ -91,6 +91,9 @@ public class ScreenRecordTileTest extends SysuiTestCase { mController, mKeyguardDismissUtil ); + + mTile.initialize(); + mTestableLooper.processAllMessages(); } // Test that the tile is inactive and labeled correctly when the controller is neither starting diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 2e0827f24bf8..fa25c3f1e005 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -289,6 +289,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testIconScrollXAfterTranslationAndReset() throws Exception { + mGroupRow.setDismissUsingRowTranslationX(false); mGroupRow.setTranslation(50); assertEquals(50, -mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java index 9ac600afe990..5bf1bb3c573f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java @@ -63,6 +63,7 @@ public class DozeParametersTest extends SysuiTestCase { @Mock private BatteryController mBatteryController; @Mock private FeatureFlags mFeatureFlags; @Mock private DumpManager mDumpManager; + @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; @Before public void setup() { @@ -75,7 +76,8 @@ public class DozeParametersTest extends SysuiTestCase { mBatteryController, mTunerService, mDumpManager, - mFeatureFlags + mFeatureFlags, + mUnlockedScreenOffAnimationController ); } @Test @@ -125,7 +127,8 @@ public class DozeParametersTest extends SysuiTestCase { when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1"); when(mFeatureFlags.useNewLockscreenAnimations()).thenReturn(true); - + when(mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation()) + .thenReturn(true); assertTrue(mDozeParameters.shouldControlUnlockedScreenOff()); // Trigger the setter for the current value. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index 9e939eefa705..2d51683c8ae5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -84,7 +84,6 @@ import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.QSDetailDisplayer; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.FeatureFlags; @@ -675,21 +674,6 @@ public class NotificationPanelViewTest extends SysuiTestCase { verify(mTapAgainViewController).show(); } - @Test - public void testNotificationClipping_isAlignedWithNotificationScrimInSplitShade() { - mStatusBarStateController.setState(SHADE); - QS qs = mock(QS.class); - when(qs.getHeader()).thenReturn(mock(View.class)); - mNotificationPanelViewController.mQs = qs; - enableSplitShade(); - - // hacky way to refresh notification scrim top with non-zero qsPanelBottom value - mNotificationPanelViewController.setTransitionToFullShadeAmount(200, false, 0); - - verify(mAmbientState) - .setNotificationScrimTop(NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE); - } - private FalsingManager.FalsingTapListener getFalsingTapListener() { for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) { listener.onViewAttachedToWindow(mView); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java index 323843098a1a..9fe47eceff1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java @@ -45,6 +45,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; import org.junit.Test; @@ -70,6 +71,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Mock private SysuiColorExtractor mColorExtractor; @Mock ColorExtractor.GradientColors mGradientColors; @Mock private DumpManager mDumpManager; + @Mock private KeyguardStateController mKeyguardStateController; @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; @@ -83,7 +85,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, - mColorExtractor, mDumpManager); + mColorExtractor, mDumpManager, mKeyguardStateController); mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); mNotificationShadeWindowController.attach(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index b2efd682bc62..cbc7c6dd0447 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -777,6 +777,12 @@ public class StatusBarTest extends SysuiTestCase { } @Test + public void testDumpBarTransitions_DoesNotCrash() { + StatusBar.dumpBarTransitions( + new PrintWriter(new ByteArrayOutputStream()), "var", /* transitions= */ null); + } + + @Test @RunWithLooper(setAsMainLooper = true) public void testUpdateKeyguardState_DoesNotCrash() { mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD); @@ -843,12 +849,14 @@ public class StatusBarTest extends SysuiTestCase { // By default, showKeyguardImpl sets state to KEYGUARD. mStatusBar.showKeyguardImpl(); - verify(mStatusBarStateController).setState(eq(StatusBarState.KEYGUARD)); + verify(mStatusBarStateController).setState( + eq(StatusBarState.KEYGUARD), eq(false) /* force */); // If useFullscreenUserSwitcher is true, state is set to FULLSCREEN_USER_SWITCHER. when(mUserSwitcherController.useFullscreenUserSwitcher()).thenReturn(true); mStatusBar.showKeyguardImpl(); - verify(mStatusBarStateController).setState(eq(StatusBarState.FULLSCREEN_USER_SWITCHER)); + verify(mStatusBarStateController).setState( + eq(StatusBarState.FULLSCREEN_USER_SWITCHER), eq(false) /* force */); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java index 1aebf1c1c80d..e136d00b86f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java @@ -132,4 +132,9 @@ public class FakeKeyguardStateController implements KeyguardStateController { public boolean canPerformSmartSpaceTransition() { return false; } + + @Override + public boolean isKeyguardScreenRotationAllowed() { + return false; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 6e2e4cb9ecfa..496976e8f55c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -98,6 +98,7 @@ import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; @@ -227,6 +228,8 @@ public class BubblesTest extends SysuiTestCase { private TaskStackListenerImpl mTaskStackListener; @Mock private ShellTaskOrganizer mShellTaskOrganizer; + @Mock + private KeyguardStateController mKeyguardStateController; private TestableBubblePositioner mPositioner; @@ -249,7 +252,7 @@ public class BubblesTest extends SysuiTestCase { mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, - mColorExtractor, mDumpManager); + mColorExtractor, mDumpManager, mKeyguardStateController); mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); mNotificationShadeWindowController.attach(); @@ -287,6 +290,7 @@ public class BubblesTest extends SysuiTestCase { // TODO: Fix mPositioner = new TestableBubblePositioner(mContext, mWindowManager); + mPositioner.setMaxBubbles(5); mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor); TestableNotificationInterruptStateProviderImpl interruptionStateProvider = diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index 9339f81940d9..9114b7a35fd2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -82,6 +82,7 @@ import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; @@ -192,6 +193,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { private TaskStackListenerImpl mTaskStackListener; @Mock private ShellTaskOrganizer mShellTaskOrganizer; + @Mock + private KeyguardStateController mKeyguardStateController; private TestableBubblePositioner mPositioner; @@ -213,7 +216,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, - mColorExtractor, mDumpManager); + mColorExtractor, mDumpManager, mKeyguardStateController); mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); mNotificationShadeWindowController.attach(); @@ -232,6 +235,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); mPositioner = new TestableBubblePositioner(mContext, mWindowManager); + mPositioner.setMaxBubbles(5); mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor); TestableNotificationInterruptStateProviderImpl interruptionStateProvider = diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java index 24a7cd5c89ac..6edc373d2926 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java @@ -22,9 +22,11 @@ import android.graphics.Insets; import android.graphics.Rect; import android.view.WindowManager; +import com.android.wm.shell.R; import com.android.wm.shell.bubbles.BubblePositioner; public class TestableBubblePositioner extends BubblePositioner { + private int mMaxBubbles; public TestableBubblePositioner(Context context, WindowManager windowManager) { @@ -33,5 +35,15 @@ public class TestableBubblePositioner extends BubblePositioner { updateInternal(Configuration.ORIENTATION_PORTRAIT, Insets.of(0, 0, 0, 0), new Rect(0, 0, 500, 1000)); + mMaxBubbles = context.getResources().getInteger(R.integer.bubbles_max_rendered); + } + + public void setMaxBubbles(int max) { + mMaxBubbles = max; + } + + @Override + public int getMaxBubbles() { + return mMaxBubbles; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index 1dd0b21bda30..ff15d0151ea4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -37,6 +37,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; +import com.android.wm.shell.onehanded.OneHandedEventCallback; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.Pip; @@ -106,6 +107,7 @@ public class WMShellTest extends SysuiTestCase { verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class)); verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class)); verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class)); + verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class)); } @Test diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 83dfe8ed2576..05131d44b01e 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -454,19 +454,13 @@ public class CompanionDeviceManagerService extends SystemService implements Bind }).cancelTimeout(); }, FgThread.getExecutor()).whenComplete(uncheckExceptions((association, err) -> { - - final long callingIdentity = Binder.clearCallingIdentity(); - try { - if (err == null) { - addAssociation(association); - } else { - Slog.e(LOG_TAG, "Failed to discover device(s)", err); - callback.onFailure("No devices found: " + err.getMessage()); - } - cleanup(); - } finally { - Binder.restoreCallingIdentity(callingIdentity); + if (err == null) { + addAssociation(association); + } else { + Slog.e(LOG_TAG, "Failed to discover device(s)", err); + callback.onFailure("No devices found: " + err.getMessage()); } + cleanup(); })); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 0802123b39da..5e388d94869d 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1877,7 +1877,6 @@ public final class ActiveServices { active.mNumActive++; } r.isForeground = true; - r.mLogEntering = true; // The logging of FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER event could // be deferred, make a copy of mAllowStartForeground and // mAllowWhileInUsePermissionInFgs. @@ -1903,6 +1902,9 @@ public final class ActiveServices { AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); registerAppOpCallbackLocked(r); mAm.updateForegroundServiceUsageStats(r.name, r.userId, true); + logFGSStateChangeLocked(r, + FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER, + 0); } // Even if the service is already a FGS, we need to update the notification, // so we need to call it again. @@ -1958,6 +1960,7 @@ public final class ActiveServices { FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT, r.mFgsExitTime > r.mFgsEnterTime ? (int)(r.mFgsExitTime - r.mFgsEnterTime) : 0); + r.mFgsNotificationWasDeferred = false; resetFgsRestrictionLocked(r); mAm.updateForegroundServiceUsageStats(r.name, r.userId, false); if (r.app != null) { @@ -2162,6 +2165,7 @@ public final class ActiveServices { } r.fgDisplayTime = when; r.mFgsNotificationDeferred = true; + r.mFgsNotificationWasDeferred = true; r.mFgsNotificationShown = false; mPendingFgsNotifications.add(r); if (DEBUG_FOREGROUND_SERVICE) { @@ -2205,11 +2209,6 @@ public final class ActiveServices { Slog.d(TAG_SERVICE, " - service no longer running/fg, ignoring"); } } - // Regardless of whether we needed to post the notification or the - // service is no longer running, we may not have logged its FGS - // transition yet depending on the timing and API sequence that led - // to this point - so make sure to do so. - maybeLogFGSStateEnteredLocked(r); } } if (DEBUG_FOREGROUND_SERVICE) { @@ -2252,16 +2251,6 @@ public final class ActiveServices { } } - private void maybeLogFGSStateEnteredLocked(ServiceRecord r) { - if (r.mLogEntering) { - logFGSStateChangeLocked(r, - FrameworkStatsLog - .FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER, - 0); - r.mLogEntering = false; - } - } - /** * Callback from NotificationManagerService whenever it posts a notification * associated with a foreground service. This is the unified handling point @@ -2280,9 +2269,7 @@ public final class ActiveServices { && id == sr.foregroundId && sr.appInfo.packageName.equals(pkg)) { // Found it. If 'shown' is false, it means that the notification - // subsystem will not be displaying it yet, so all we do is log - // the "fgs entered" transition noting deferral, then we're done. - maybeLogFGSStateEnteredLocked(sr); + // subsystem will not be displaying it yet. if (shown) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "Notification shown; canceling deferral of " @@ -4240,6 +4227,7 @@ public final class ActiveServices { r.isForeground = false; r.foregroundId = 0; r.foregroundNoti = null; + r.mFgsNotificationWasDeferred = false; resetFgsRestrictionLocked(r); // Clear start entries. @@ -6263,7 +6251,7 @@ public final class ActiveServices { ? r.mRecentCallerApplicationInfo.targetSdkVersion : 0, r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mCallingUid : INVALID_UID, - r.mFgsNotificationDeferred, + r.mFgsNotificationWasDeferred, r.mFgsNotificationShown, durationMs, r.mStartForegroundCount, diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 4e6e91ac7b5d..7f6e6687625c 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -78,6 +78,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsHelper; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.BatteryUsageStatsProvider; +import com.android.internal.os.BatteryUsageStatsStore; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.PowerProfile; import com.android.internal.os.RailStats; @@ -124,10 +125,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub Watchdog.Monitor { static final String TAG = "BatteryStatsService"; static final boolean DBG = false; + private static final boolean BATTERY_USAGE_STORE_ENABLED = true; private static IBatteryStats sService; final BatteryStatsImpl mStats; + private final BatteryUsageStatsStore mBatteryUsageStatsStore; private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider; private final Context mContext; private final BatteryExternalStatsWorker mWorker; @@ -348,7 +351,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub mStats.setPowerProfileLocked(new PowerProfile(context)); mStats.startTrackingSystemServerCpuTime(); - mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats); + if (BATTERY_USAGE_STORE_ENABLED) { + mBatteryUsageStatsStore = + new BatteryUsageStatsStore(context, mStats, systemDir, mHandler); + } else { + mBatteryUsageStatsStore = null; + } + mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats, + mBatteryUsageStatsStore); } public void publish() { @@ -752,6 +762,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL, null, // use default PullAtomMetadata values BackgroundThread.getExecutor(), pullAtomCallback); + statsManager.setPullAtomCallback( + FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET, + null, // use default PullAtomMetadata values + BackgroundThread.getExecutor(), pullAtomCallback); } /** StatsPullAtomCallback for pulling BatteryUsageStats data. */ @@ -768,6 +782,17 @@ public final class BatteryStatsService extends IBatteryStats.Stub new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(); bus = getBatteryUsageStats(List.of(powerProfileQuery)).get(0); break; + case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: + final long sessionStart = mBatteryUsageStatsStore + .getLastBatteryUsageStatsBeforeResetAtomPullTimestamp(); + final long sessionEnd = mStats.getStartClockTime(); + final BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() + .aggregateSnapshots(sessionStart, sessionEnd) + .build(); + bus = getBatteryUsageStats(List.of(query)).get(0); + mBatteryUsageStatsStore + .setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd); + break; default: throw new UnsupportedOperationException("Unknown tagId=" + atomTag); } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 62286379ba33..aef402ac3213 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -328,7 +328,8 @@ public class OomAdjuster { final int index = mCache.indexOfKey(app.packageName); Pair<Boolean, WeakReference<ApplicationInfo>> p; if (index < 0) { - p = new Pair<>(mPlatformCompat.isChangeEnabled(mChangeId, app), + p = new Pair<>(mPlatformCompat.isChangeEnabledInternalNoLogging(mChangeId, + app), new WeakReference<>(app)); mCache.put(app.packageName, p); return p.first; @@ -338,7 +339,8 @@ public class OomAdjuster { return p.first; } // Cache is invalid, regenerate it - p = new Pair<>(mPlatformCompat.isChangeEnabled(mChangeId, app), + p = new Pair<>(mPlatformCompat.isChangeEnabledInternalNoLogging(mChangeId, + app), new WeakReference<>(app)); mCache.setValueAt(index, p); return p.first; diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 3ba07af710c2..141f081ab9a7 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -109,7 +109,6 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN boolean fgWaiting; // is a timeout for going foreground already scheduled? boolean isNotAppComponentUsage; // is service binding not considered component/package usage? boolean isForeground; // is service currently in foreground mode? - boolean mLogEntering; // need to report fgs transition once deferral policy is known int foregroundId; // Notification ID of last foreground req. Notification foregroundNoti; // Notification record of foreground state. long fgDisplayTime; // time at which the FGS notification should become visible @@ -167,8 +166,10 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN long mFgsEnterTime = 0; // The uptime when the service exits FGS state. long mFgsExitTime = 0; - // FGS notification was deferred. + // FGS notification is deferred. boolean mFgsNotificationDeferred; + // FGS notification was deferred. + boolean mFgsNotificationWasDeferred; // FGS notification was shown before the FGS finishes, or it wasn't deferred in the first place. boolean mFgsNotificationShown; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 104bc9b2c8d1..99a33e4462e2 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -848,7 +848,7 @@ public class AppOpsService extends IAppOpsService.Stub { return mAttributionFlags; } - /** @return attributoin chiang id for the access */ + /** @return attribution chain id for the access */ public int getAttributionChainId() { return mAttributionChainId; } @@ -912,7 +912,8 @@ public class AppOpsService extends IAppOpsService.Stub { proxyAttributionTag, uidState, flags); mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, - tag, uidState, flags, accessTime); + tag, uidState, flags, accessTime, AppOpsManager.ATTRIBUTION_FLAGS_NONE, + AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); } /** @@ -1053,9 +1054,9 @@ public class AppOpsService extends IAppOpsService.Stub { event.numUnfinishedStarts++; if (isStarted) { - // TODO: Consider storing the attribution chain flags and id mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, uidState, flags, startTime); + parent.packageName, tag, uidState, flags, startTime, attributionFlags, + attributionChainId); } } @@ -1112,7 +1113,8 @@ public class AppOpsService extends IAppOpsService.Stub { mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, parent.packageName, tag, event.getUidState(), - event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration()); + event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(), + event.getAttributionFlags(), event.getAttributionChainId()); if (!isPausing) { mInProgressStartOpEventPool.release(event); @@ -1215,7 +1217,8 @@ public class AppOpsService extends IAppOpsService.Stub { event.mStartElapsedTime = SystemClock.elapsedRealtime(); event.mStartTime = startTime; mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, event.mUidState, event.mFlags, startTime); + parent.packageName, tag, event.mUidState, event.mFlags, startTime, + event.getAttributionFlags(), event.getAttributionChainId()); if (shouldSendActive) { scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, tag, true, event.getAttributionFlags(), event.getAttributionChainId()); diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java index 10cfddfb3ae4..49469cc8a597 100644 --- a/services/core/java/com/android/server/appop/DiscreteRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java @@ -26,6 +26,7 @@ import static android.app.AppOpsManager.OP_FINE_LOCATION; import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_FLAG_SELF; import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; +import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY; import static android.app.AppOpsManager.OP_NONE; import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA; import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE; @@ -156,8 +157,11 @@ final class DiscreteRegistry { private static final String ATTR_NOTE_DURATION = "nd"; private static final String ATTR_UID_STATE = "us"; private static final String ATTR_FLAGS = "f"; + private static final String ATTR_ATTRIBUTION_FLAGS = "af"; + private static final String ATTR_CHAIN_ID = "ci"; - private static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED; + private static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED + | OP_FLAG_TRUSTED_PROXY; // Lock for read/write access to on disk state private final Object mOnDiskLock = new Object(); @@ -227,13 +231,14 @@ final class DiscreteRegistry { void recordDiscreteAccess(int uid, String packageName, int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, - long accessDuration) { + long accessDuration, @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId) { if (!isDiscreteOp(op, flags)) { return; } synchronized (mInMemoryLock) { mDiscreteOps.addDiscreteAccess(op, uid, packageName, attributionTag, flags, uidState, - accessTime, accessDuration); + accessTime, accessDuration, attributionFlags, attributionChainId); } } @@ -383,9 +388,10 @@ final class DiscreteRegistry { void addDiscreteAccess(int op, int uid, @NonNull String packageName, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, - @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) { + @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { getOrCreateDiscreteUidOps(uid).addDiscreteAccess(op, packageName, attributionTag, flags, - uidState, accessTime, accessDuration); + uidState, accessTime, accessDuration, attributionFlags, attributionChainId); } private void filter(long beginTimeMillis, long endTimeMillis, @@ -613,9 +619,10 @@ final class DiscreteRegistry { void addDiscreteAccess(int op, @NonNull String packageName, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, - long accessTime, long accessDuration) { + long accessTime, long accessDuration, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { getOrCreateDiscretePackageOps(packageName).addDiscreteAccess(op, attributionTag, flags, - uidState, accessTime, accessDuration); + uidState, accessTime, accessDuration, attributionFlags, attributionChainId); } private DiscretePackageOps getOrCreateDiscretePackageOps(String packageName) { @@ -680,9 +687,10 @@ final class DiscreteRegistry { void addDiscreteAccess(int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, - long accessTime, long accessDuration) { + long accessTime, long accessDuration, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { getOrCreateDiscreteOp(op).addDiscreteAccess(attributionTag, flags, uidState, accessTime, - accessDuration); + accessDuration, attributionFlags, attributionChainId); } void merge(DiscretePackageOps other) { @@ -823,37 +831,39 @@ final class DiscreteRegistry { for (int j = 0; j < n; j++) { DiscreteOpEvent event = list.get(j); list.set(j, new DiscreteOpEvent(event.mNoteTime - offset, event.mNoteDuration, - event.mUidState, event.mOpFlag)); + event.mUidState, event.mOpFlag, event.mAttributionFlags, + event.mAttributionChainId)); } } } void addDiscreteAccess(@Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, - long accessTime, long accessDuration) { + long accessTime, long accessDuration, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { List<DiscreteOpEvent> attributedOps = getOrCreateDiscreteOpEventsList( attributionTag); - accessTime = accessTime / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; - accessDuration = accessDuration == -1 ? -1 - : (accessDuration + sDiscreteHistoryQuantization - 1) - / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; int nAttributedOps = attributedOps.size(); int i = nAttributedOps; for (; i > 0; i--) { DiscreteOpEvent previousOp = attributedOps.get(i - 1); - if (previousOp.mNoteTime < accessTime) { + if (discretizeTimeStamp(previousOp.mNoteTime) < discretizeTimeStamp(accessTime)) { break; } - if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState) { - if (accessDuration != previousOp.mNoteDuration) { + if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState + && previousOp.mAttributionFlags == attributionFlags + && previousOp.mAttributionChainId == attributionChainId) { + if (discretizeDuration(accessDuration) != discretizeDuration( + previousOp.mNoteDuration)) { break; } else { return; } } } - attributedOps.add(i, new DiscreteOpEvent(accessTime, accessDuration, uidState, flags)); + attributedOps.add(i, new DiscreteOpEvent(accessTime, accessDuration, uidState, flags, + attributionFlags, attributionChainId)); } private List<DiscreteOpEvent> getOrCreateDiscreteOpEventsList(String attributionTag) { @@ -875,7 +885,8 @@ final class DiscreteRegistry { for (int j = 0; j < nEvents; j++) { DiscreteOpEvent event = events.get(j); result.addDiscreteAccess(op, uid, packageName, tag, event.mUidState, - event.mOpFlag, event.mNoteTime, event.mNoteDuration); + event.mOpFlag, discretizeTimeStamp(event.mNoteTime), + discretizeDuration(event.mNoteDuration)); } } } @@ -932,11 +943,15 @@ final class DiscreteRegistry { -1); int uidState = parser.getAttributeInt(null, ATTR_UID_STATE); int opFlags = parser.getAttributeInt(null, ATTR_FLAGS); + int attributionFlags = parser.getAttributeInt(null, + ATTR_ATTRIBUTION_FLAGS, AppOpsManager.ATTRIBUTION_FLAGS_NONE); + int attributionChainId = parser.getAttributeInt(null, ATTR_CHAIN_ID, + AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); if (noteTime + noteDuration < beginTimeMillis) { continue; } DiscreteOpEvent event = new DiscreteOpEvent(noteTime, noteDuration, - uidState, opFlags); + uidState, opFlags, attributionFlags, attributionChainId); events.add(event); } } @@ -952,13 +967,18 @@ final class DiscreteRegistry { final long mNoteDuration; final @AppOpsManager.UidState int mUidState; final @AppOpsManager.OpFlags int mOpFlag; + final @AppOpsManager.AttributionFlags int mAttributionFlags; + final int mAttributionChainId; DiscreteOpEvent(long noteTime, long noteDuration, @AppOpsManager.UidState int uidState, - @AppOpsManager.OpFlags int opFlag) { + @AppOpsManager.OpFlags int opFlag, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { mNoteTime = noteTime; mNoteDuration = noteDuration; mUidState = uidState; mOpFlag = opFlag; + mAttributionFlags = attributionFlags; + mAttributionChainId = attributionChainId; } private void dump(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf, @@ -969,13 +989,19 @@ final class DiscreteRegistry { pw.print("-"); pw.print(flagsToString(mOpFlag)); pw.print("] at "); - date.setTime(mNoteTime); + date.setTime(discretizeTimeStamp(mNoteTime)); pw.print(sdf.format(date)); if (mNoteDuration != -1) { pw.print(" for "); - pw.print(mNoteDuration); + pw.print(discretizeDuration(mNoteDuration)); pw.print(" milliseconds "); } + if (mAttributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE) { + pw.print(" attribution flags="); + pw.print(mAttributionFlags); + pw.print(" with chainId="); + pw.print(mAttributionChainId); + } pw.println(); } @@ -984,6 +1010,12 @@ final class DiscreteRegistry { if (mNoteDuration != -1) { out.attributeLong(null, ATTR_NOTE_DURATION, mNoteDuration); } + if (mAttributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE) { + out.attributeInt(null, ATTR_ATTRIBUTION_FLAGS, mAttributionFlags); + } + if (mAttributionChainId != AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE) { + out.attributeInt(null, ATTR_CHAIN_ID, mAttributionChainId); + } out.attributeInt(null, ATTR_UID_STATE, mUidState); out.attributeInt(null, ATTR_FLAGS, mOpFlag); } @@ -1055,6 +1087,16 @@ final class DiscreteRegistry { return true; } + private static long discretizeTimeStamp(long timeStamp) { + return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; + + } + + private static long discretizeDuration(long duration) { + return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1) + / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; + } + void setDebugMode(boolean debugMode) { this.mDebugMode = debugMode; } diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index 8b72be78a7f6..dd5df503d936 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -487,7 +487,8 @@ final class HistoricalRegistry { void incrementOpAccessedCount(int op, int uid, @NonNull String packageName, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, - long accessTime) { + long accessTime, @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -499,7 +500,7 @@ final class HistoricalRegistry { attributionTag, uidState, flags, 1); mDiscreteRegistry.recordDiscreteAccess(uid, packageName, op, attributionTag, - flags, uidState, accessTime, -1); + flags, uidState, accessTime, -1, attributionFlags, attributionChainId); } } } @@ -521,7 +522,8 @@ final class HistoricalRegistry { void increaseOpAccessDuration(int op, int uid, @NonNull String packageName, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, - long eventStartTime, long increment) { + long eventStartTime, long increment, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -532,7 +534,8 @@ final class HistoricalRegistry { System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName, attributionTag, uidState, flags, increment); mDiscreteRegistry.recordDiscreteAccess(uid, packageName, op, attributionTag, - flags, uidState, eventStartTime, increment); + flags, uidState, eventStartTime, increment, attributionFlags, + attributionChainId); } } } diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java index 6db75eb80aea..a7e1a2876f81 100644 --- a/services/core/java/com/android/server/display/WifiDisplayController.java +++ b/services/core/java/com/android/server/display/WifiDisplayController.java @@ -550,11 +550,6 @@ final class WifiDisplayController implements DumpUtils.Dump { private void disconnect() { mDesiredDevice = null; - mWifiP2pManager = null; - if (null != mWifiP2pChannel) { - mWifiP2pChannel.close(); - mWifiP2pChannel = null; - } updateConnection(); } diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index b9e97e89051b..aeb1893c78b6 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -65,7 +65,7 @@ import android.location.LastLocationRequest; import android.location.Location; import android.location.LocationManager; import android.location.LocationManagerInternal; -import android.location.LocationManagerInternal.OnProviderLocationTagsChangeListener; +import android.location.LocationManagerInternal.LocationPackageTagsListener; import android.location.LocationProvider; import android.location.LocationRequest; import android.location.LocationTime; @@ -93,6 +93,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; +import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.location.eventlog.LocationEventLog; @@ -259,7 +260,7 @@ public class LocationManagerService extends ILocationManager.Stub implements new CopyOnWriteArrayList<>(); @GuardedBy("mLock") - @Nullable OnProviderLocationTagsChangeListener mOnProviderLocationTagsChangeListener; + @Nullable LocationPackageTagsListener mLocationTagsChangedListener; LocationManagerService(Context context, Injector injector) { mContext = context.createAttributionContext(ATTRIBUTION_TAG); @@ -1363,32 +1364,28 @@ public class LocationManagerService extends ILocationManager.Stub implements refreshAppOpsRestrictions(UserHandle.USER_ALL); } - OnProviderLocationTagsChangeListener listener; - synchronized (mLock) { - listener = mOnProviderLocationTagsChangeListener; - } - - if (listener != null) { - if (!oldState.extraAttributionTags.equals(newState.extraAttributionTags) - || !Objects.equals(oldState.identity, newState.identity)) { - if (oldState.identity != null) { - listener.onLocationTagsChanged( - new LocationManagerInternal.LocationTagInfo( - oldState.identity.getUid(), - oldState.identity.getPackageName(), - Collections.emptySet())); - } - if (newState.identity != null) { - ArraySet<String> attributionTags = new ArraySet<>( - newState.extraAttributionTags.size() + 1); - attributionTags.addAll(newState.extraAttributionTags); - attributionTags.add(newState.identity.getAttributionTag()); - - listener.onLocationTagsChanged( - new LocationManagerInternal.LocationTagInfo( - newState.identity.getUid(), - newState.identity.getPackageName(), - attributionTags)); + if (!oldState.extraAttributionTags.equals(newState.extraAttributionTags) + || !Objects.equals(oldState.identity, newState.identity)) { + // since we're potentially affecting the tag lists for two different uids, acquire the + // lock to ensure providers cannot change while we're looping over the providers + // multiple times, which could lead to inconsistent results. + synchronized (mLock) { + LocationPackageTagsListener listener = mLocationTagsChangedListener; + if (listener != null) { + int oldUid = oldState.identity != null ? oldState.identity.getUid() : -1; + int newUid = newState.identity != null ? newState.identity.getUid() : -1; + if (oldUid != -1) { + PackageTagsList tags = calculateAppOpsLocationSourceTags(oldUid); + FgThread.getHandler().post( + () -> listener.onLocationPackageTagsChanged(oldUid, tags)); + } + // if the new app id is the same as the old app id, no need to invoke the + // listener twice, it's already been taken care of + if (newUid != -1 && newUid != oldUid) { + PackageTagsList tags = calculateAppOpsLocationSourceTags(newUid); + FgThread.getHandler().post( + () -> listener.onLocationPackageTagsChanged(newUid, tags)); + } } } } @@ -1436,6 +1433,31 @@ public class LocationManagerService extends ILocationManager.Stub implements userId); } + PackageTagsList calculateAppOpsLocationSourceTags(int uid) { + PackageTagsList.Builder builder = new PackageTagsList.Builder(); + for (LocationProviderManager manager : mProviderManagers) { + AbstractLocationProvider.State managerState = manager.getState(); + if (managerState.identity == null) { + continue; + } + if (managerState.identity.getUid() != uid) { + continue; + } + + builder.add(managerState.identity.getPackageName(), managerState.extraAttributionTags); + if (managerState.extraAttributionTags.isEmpty() + || managerState.identity.getAttributionTag() != null) { + builder.add(managerState.identity.getPackageName(), + managerState.identity.getAttributionTag()); + } else { + Log.e(TAG, manager.getName() + " provider has specified a null attribution tag and " + + "a non-empty set of extra attribution tags - dropping the null " + + "attribution tag"); + } + } + return builder.build(); + } + private class LocalService extends LocationManagerInternal { LocalService() {} @@ -1506,10 +1528,29 @@ public class LocationManagerService extends ILocationManager.Stub implements } @Override - public void setOnProviderLocationTagsChangeListener( - @Nullable OnProviderLocationTagsChangeListener listener) { + public void setLocationPackageTagsListener( + @Nullable LocationPackageTagsListener listener) { synchronized (mLock) { - mOnProviderLocationTagsChangeListener = listener; + mLocationTagsChangedListener = listener; + + // calculate initial tag list and send to listener + if (listener != null) { + ArraySet<Integer> uids = new ArraySet<>(mProviderManagers.size()); + for (LocationProviderManager manager : mProviderManagers) { + CallerIdentity identity = manager.getIdentity(); + if (identity != null) { + uids.add(identity.getUid()); + } + } + + for (int uid : uids) { + PackageTagsList tags = calculateAppOpsLocationSourceTags(uid); + if (!tags.isEmpty()) { + FgThread.getHandler().post( + () -> listener.onLocationPackageTagsChanged(uid, tags)); + } + } + } } } } diff --git a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java index 1da45bd49767..eb7b77a2234f 100644 --- a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java @@ -263,10 +263,10 @@ public abstract class AbstractLocationProvider { } /** - * The current allowed state of this provider. + * The current state of the provider. */ - public final boolean isAllowed() { - return mInternalState.get().state.allowed; + public final State getState() { + return mInternalState.get().state; } /** @@ -277,13 +277,6 @@ public abstract class AbstractLocationProvider { } /** - * The current provider properties of this provider. - */ - public final @Nullable ProviderProperties getProperties() { - return mInternalState.get().state.properties; - } - - /** * Call this method to report a change in provider properties. */ protected void setProperties(@Nullable ProviderProperties properties) { @@ -291,13 +284,6 @@ public abstract class AbstractLocationProvider { } /** - * The current identity of this provider. - */ - public final @Nullable CallerIdentity getIdentity() { - return mInternalState.get().state.identity; - } - - /** * Call this method to report a change in the provider's identity. */ protected void setIdentity(@Nullable CallerIdentity identity) { diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 4f8b87b51294..8d335b83d99c 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -1407,12 +1407,16 @@ public class LocationProviderManager extends return mName; } + public AbstractLocationProvider.State getState() { + return mProvider.getState(); + } + public @Nullable CallerIdentity getIdentity() { - return mProvider.getIdentity(); + return mProvider.getState().identity; } public @Nullable ProviderProperties getProperties() { - return mProvider.getProperties(); + return mProvider.getState().properties; } public boolean hasProvider() { @@ -2403,7 +2407,7 @@ public class LocationProviderManager extends Preconditions.checkArgument(userId >= 0); boolean enabled = mState == STATE_STARTED - && mProvider.isAllowed() + && mProvider.getState().allowed && mSettingsHelper.isLocationEnabled(userId); int index = mEnabled.indexOfKey(userId); diff --git a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java index 021e8dbdb44e..22295fe7ba28 100644 --- a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java @@ -21,9 +21,7 @@ import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import android.annotation.Nullable; import android.location.Location; import android.location.LocationResult; -import android.location.provider.ProviderProperties; import android.location.provider.ProviderRequest; -import android.location.util.identity.CallerIdentity; import android.os.Bundle; import com.android.internal.annotations.GuardedBy; @@ -32,7 +30,6 @@ import com.android.internal.util.Preconditions; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collections; -import java.util.Set; /** * Represents a location provider that may switch between a mock implementation and a real @@ -290,21 +287,21 @@ public class MockableLocationProvider extends AbstractLocationProvider { Preconditions.checkState(!Thread.holdsLock(mOwnerLock)); AbstractLocationProvider provider; + State providerState; synchronized (mOwnerLock) { provider = mProvider; - pw.println("allowed=" + isAllowed()); - CallerIdentity identity = getIdentity(); - if (identity != null) { - pw.println("identity=" + identity); - } - Set<String> extraAttributionTags = getExtraAttributionTags(); - if (!extraAttributionTags.isEmpty()) { - pw.println("extra attribution tags=" + extraAttributionTags); - } - ProviderProperties properties = getProperties(); - if (properties != null) { - pw.println("properties=" + properties); - } + providerState = getState(); + } + + pw.println("allowed=" + providerState.allowed); + if (providerState.identity != null) { + pw.println("identity=" + providerState.identity); + } + if (!providerState.extraAttributionTags.isEmpty()) { + pw.println("extra attribution tags=" + providerState.extraAttributionTags); + } + if (providerState.properties != null) { + pw.println("properties=" + providerState.properties); } if (provider != null) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index b26485b5aad9..a3f3a3a503c8 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -4785,7 +4785,7 @@ public class NotificationManagerService extends SystemService { } @Override - public String addAutomaticZenRule(AutomaticZenRule automaticZenRule) { + public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String pkg) { Objects.requireNonNull(automaticZenRule, "automaticZenRule is null"); Objects.requireNonNull(automaticZenRule.getName(), "Name is null"); if (automaticZenRule.getOwner() == null @@ -4794,6 +4794,7 @@ public class NotificationManagerService extends SystemService { "Rule must have a conditionproviderservice and/or configuration activity"); } Objects.requireNonNull(automaticZenRule.getConditionId(), "ConditionId is null"); + checkCallerIsSameApp(pkg); if (automaticZenRule.getZenPolicy() != null && automaticZenRule.getInterruptionFilter() != INTERRUPTION_FILTER_PRIORITY) { throw new IllegalArgumentException("ZenPolicy is only applicable to " @@ -4801,7 +4802,7 @@ public class NotificationManagerService extends SystemService { } enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule"); - return mZenModeHelper.addAutomaticZenRule(automaticZenRule, + return mZenModeHelper.addAutomaticZenRule(pkg, automaticZenRule, "addAutomaticZenRule"); } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 4cb6c3ba20d8..a98f113a9d57 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -307,7 +307,8 @@ public class ZenModeHelper { return null; } - public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) { + public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule, + String reason) { if (!isSystemRule(automaticZenRule)) { PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner()); if (component == null) { @@ -340,7 +341,7 @@ public class ZenModeHelper { } newConfig = mConfig.copy(); ZenRule rule = new ZenRule(); - populateZenRule(automaticZenRule, rule, true); + populateZenRule(pkg, automaticZenRule, rule, true); newConfig.automaticRules.put(rule.id, rule); if (setConfigLocked(newConfig, reason, rule.component, true)) { return rule.id; @@ -376,7 +377,7 @@ public class ZenModeHelper { ? AUTOMATIC_RULE_STATUS_ENABLED : AUTOMATIC_RULE_STATUS_DISABLED); } - populateZenRule(automaticZenRule, rule, false); + populateZenRule(rule.pkg, automaticZenRule, rule, false); return setConfigLocked(newConfig, reason, rule.component, true); } } @@ -585,7 +586,8 @@ public class ZenModeHelper { return null; } - private void populateZenRule(AutomaticZenRule automaticZenRule, ZenRule rule, boolean isNew) { + private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule, + boolean isNew) { rule.name = automaticZenRule.getName(); rule.condition = null; rule.conditionId = automaticZenRule.getConditionId(); @@ -600,9 +602,7 @@ public class ZenModeHelper { rule.id = ZenModeConfig.newRuleId(); rule.creationTime = System.currentTimeMillis(); rule.component = automaticZenRule.getOwner(); - rule.pkg = (rule.component != null) - ? rule.component.getPackageName() - : rule.configurationActivity.getPackageName(); + rule.pkg = pkg; } if (rule.enabled != automaticZenRule.isEnabled()) { @@ -611,10 +611,13 @@ public class ZenModeHelper { } protected AutomaticZenRule createAutomaticZenRule(ZenRule rule) { - return new AutomaticZenRule(rule.name, rule.component, rule.configurationActivity, + AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component, + rule.configurationActivity, rule.conditionId, rule.zenPolicy, NotificationManager.zenModeToInterruptionFilter(rule.zenMode), rule.enabled, rule.creationTime); + azr.setPackageName(rule.pkg); + return azr; } public void setManualZenMode(int zenMode, Uri conditionId, String caller, String reason) { diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 9370b14341dc..5b2c80903ce5 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -1000,16 +1000,15 @@ public class LauncherAppsService extends SystemService { intents[0].setSourceBounds(sourceBounds); // Replace theme for splash screen - final int splashScreenThemeResId = - mShortcutServiceInternal.getShortcutStartingThemeResId(getCallingUserId(), + final String splashScreenThemeResName = + mShortcutServiceInternal.getShortcutStartingThemeResName(getCallingUserId(), callingPackage, packageName, shortcutId, targetUserId); - if (splashScreenThemeResId != 0) { + if (splashScreenThemeResName != null && !splashScreenThemeResName.isEmpty()) { if (startActivityOptions == null) { startActivityOptions = new Bundle(); } - startActivityOptions.putInt(KEY_SPLASH_SCREEN_THEME, splashScreenThemeResId); + startActivityOptions.putString(KEY_SPLASH_SCREEN_THEME, splashScreenThemeResName); } - return startShortcutIntentsAsPublisher( intents, packageName, featureId, startActivityOptions, targetUserId); } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 1bd9e5eedb84..b4bd086af272 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -139,7 +139,7 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String ATTR_BITMAP_PATH = "bitmap-path"; private static final String ATTR_ICON_URI = "icon-uri"; private static final String ATTR_LOCUS_ID = "locus-id"; - private static final String ATTR_SPLASH_SCREEN_THEME_ID = "splash-screen-theme-id"; + private static final String ATTR_SPLASH_SCREEN_THEME_NAME = "splash-screen-theme-name"; private static final String ATTR_PERSON_NAME = "name"; private static final String ATTR_PERSON_URI = "uri"; @@ -1799,7 +1799,7 @@ class ShortcutPackage extends ShortcutPackageItem { ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle()); ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId()); ShortcutService.writeAttr(out, ATTR_TITLE_RES_NAME, si.getTitleResName()); - ShortcutService.writeAttr(out, ATTR_SPLASH_SCREEN_THEME_ID, si.getStartingThemeResId()); + ShortcutService.writeAttr(out, ATTR_SPLASH_SCREEN_THEME_NAME, si.getStartingThemeResName()); ShortcutService.writeAttr(out, ATTR_TEXT, si.getText()); ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId()); ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName()); @@ -2010,7 +2010,7 @@ class ShortcutPackage extends ShortcutPackageItem { String bitmapPath; String iconUri; final String locusIdString; - int splashScreenThemeResId; + String splashScreenThemeResName; int backupVersionCode; ArraySet<String> categories = null; ArrayList<Person> persons = new ArrayList<>(); @@ -2021,8 +2021,8 @@ class ShortcutPackage extends ShortcutPackageItem { title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE); titleResId = ShortcutService.parseIntAttribute(parser, ATTR_TITLE_RES_ID); titleResName = ShortcutService.parseStringAttribute(parser, ATTR_TITLE_RES_NAME); - splashScreenThemeResId = ShortcutService.parseIntAttribute(parser, - ATTR_SPLASH_SCREEN_THEME_ID); + splashScreenThemeResName = ShortcutService.parseStringAttribute(parser, + ATTR_SPLASH_SCREEN_THEME_NAME); text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT); textResId = ShortcutService.parseIntAttribute(parser, ATTR_TEXT_RES_ID); textResName = ShortcutService.parseStringAttribute(parser, ATTR_TEXT_RES_NAME); @@ -2117,7 +2117,7 @@ class ShortcutPackage extends ShortcutPackageItem { rank, extras, lastChangedTimestamp, flags, iconResId, iconResName, bitmapPath, iconUri, disabledReason, persons.toArray(new Person[persons.size()]), locusId, - splashScreenThemeResId); + splashScreenThemeResName); } private static Intent parseIntent(TypedXmlPullParser parser) diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java index c06f01a463ad..b86c50b23687 100644 --- a/services/core/java/com/android/server/pm/ShortcutParser.java +++ b/services/core/java/com/android/server/pm/ShortcutParser.java @@ -383,8 +383,11 @@ public class ShortcutParser { final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutLongLabel, 0); final int disabledMessageResId = sa.getResourceId( R.styleable.Shortcut_shortcutDisabledMessage, 0); - final int splashScreenTheme = sa.getResourceId( + final int splashScreenThemeResId = sa.getResourceId( R.styleable.Shortcut_splashScreenTheme, 0); + final String splashScreenThemeResName = splashScreenThemeResId != 0 + ? service.mContext.getResources().getResourceName(splashScreenThemeResId) + : null; if (TextUtils.isEmpty(id)) { Log.w(TAG, "android:shortcutId must be provided. activity=" + activity); @@ -407,7 +410,7 @@ public class ShortcutParser { rank, iconResId, enabled, - splashScreenTheme); + splashScreenThemeResName); } finally { sa.recycle(); } @@ -416,7 +419,7 @@ public class ShortcutParser { private static ShortcutInfo createShortcutFromManifest(ShortcutService service, @UserIdInt int userId, String id, String packageName, ComponentName activityComponent, int titleResId, int textResId, int disabledMessageResId, - int rank, int iconResId, boolean enabled, int splashScreenTheme) { + int rank, int iconResId, boolean enabled, @Nullable String splashScreenThemeResName) { final int flags = (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED) @@ -456,7 +459,7 @@ public class ShortcutParser { disabledReason, null /* persons */, null /* locusId */, - splashScreenTheme); + splashScreenThemeResName); } private static String parseCategory(ShortcutService service, AttributeSet attrs) { diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 5f1027797292..fcbf40e29933 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -3536,7 +3536,8 @@ public class ShortcutService extends IShortcutService.Stub { } @Override - public int getShortcutStartingThemeResId(int launcherUserId, + @Nullable + public String getShortcutStartingThemeResName(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId) { Objects.requireNonNull(callingPackage, "callingPackage"); @@ -3553,11 +3554,11 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutPackage p = getUserShortcutsLocked(userId) .getPackageShortcutsIfExists(packageName); if (p == null) { - return 0; + return null; } final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); - return shortcutInfo != null ? shortcutInfo.getStartingThemeResId() : 0; + return shortcutInfo != null ? shortcutInfo.getStartingThemeResName() : null; } } diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java index 94005b2c8361..22c370ef4dbe 100644 --- a/services/core/java/com/android/server/policy/AppOpsPolicy.java +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -33,14 +33,15 @@ import android.content.pm.ResolveInfo; import android.location.LocationManagerInternal; import android.net.Uri; import android.os.IBinder; +import android.os.PackageTagsList; import android.os.Process; import android.os.UserHandle; import android.service.voice.VoiceInteractionManagerInternal; import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; import android.text.TextUtils; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.function.DecFunction; @@ -52,9 +53,9 @@ import com.android.internal.util.function.TriFunction; import com.android.internal.util.function.UndecFunction; import com.android.server.LocalServices; +import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -67,11 +68,10 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat "android:activity_recognition_allow_listed_tags"; private static final String ACTIVITY_RECOGNITION_TAGS_SEPARATOR = ";"; - private static ArraySet<String> sExpectedTags = new ArraySet<>(new String[] { + private static final ArraySet<String> sExpectedTags = new ArraySet<>(new String[] { "awareness_provider", "activity_recognition_provider", "network_location_provider", "network_location_calibration", "fused_location_provider", "geofencer_provider"}); - @NonNull private final Object mLock = new Object(); @@ -96,13 +96,22 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat */ @GuardedBy("mLock - writes only - see above") @NonNull - private final ConcurrentHashMap<Integer, ArrayMap<String, ArraySet<String>>> mLocationTags = + private final ConcurrentHashMap<Integer, PackageTagsList> mLocationTags = new ConcurrentHashMap<>(); + // location tags can vary per uid - but we merge all tags under an app id into the final data + // structure above + @GuardedBy("mLock") + private final SparseArray<PackageTagsList> mPerUidLocationTags = new SparseArray<>(); + + // activity recognition currently only grabs tags from the APK manifest. we know that the + // manifest is the same for all users, so there's no need to track variations in tags across + // different users. if that logic ever changes, this might need to behave more like location + // tags above. @GuardedBy("mLock - writes only - see above") @NonNull - private final ConcurrentHashMap<Integer, ArrayMap<String, ArraySet<String>>> - mActivityRecognitionTags = new ConcurrentHashMap<>(); + private final ConcurrentHashMap<Integer, PackageTagsList> mActivityRecognitionTags = + new ConcurrentHashMap<>(); public AppOpsPolicy(@NonNull Context context) { mContext = context; @@ -112,13 +121,28 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat final LocationManagerInternal locationManagerInternal = LocalServices.getService( LocationManagerInternal.class); - locationManagerInternal.setOnProviderLocationTagsChangeListener((providerTagInfo) -> { - synchronized (mLock) { - updateAllowListedTagsForPackageLocked(providerTagInfo.getUid(), - providerTagInfo.getPackageName(), providerTagInfo.getTags(), - mLocationTags); - } - }); + locationManagerInternal.setLocationPackageTagsListener( + (uid, packageTagsList) -> { + synchronized (mLock) { + if (packageTagsList.isEmpty()) { + mPerUidLocationTags.remove(uid); + } else { + mPerUidLocationTags.set(uid, packageTagsList); + } + + int appId = UserHandle.getAppId(uid); + PackageTagsList.Builder appIdTags = new PackageTagsList.Builder(1); + int size = mPerUidLocationTags.size(); + for (int i = 0; i < size; i++) { + if (UserHandle.getAppId(mPerUidLocationTags.keyAt(i)) == appId) { + appIdTags.add(mPerUidLocationTags.valueAt(i)); + } + } + + updateAllowListedTagsForPackageLocked(appId, appIdTags.build(), + mLocationTags); + } + }); final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); @@ -306,95 +330,30 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat } final String tagsList = resolvedService.serviceInfo.metaData.getString( ACTIVITY_RECOGNITION_TAGS); - if (tagsList != null) { - final String[] tags = tagsList.split(ACTIVITY_RECOGNITION_TAGS_SEPARATOR); + if (!TextUtils.isEmpty(tagsList)) { + PackageTagsList packageTagsList = new PackageTagsList.Builder(1).add( + resolvedService.serviceInfo.packageName, + Arrays.asList(tagsList.split(ACTIVITY_RECOGNITION_TAGS_SEPARATOR))).build(); synchronized (mLock) { updateAllowListedTagsForPackageLocked( - resolvedService.serviceInfo.applicationInfo.uid, - resolvedService.serviceInfo.packageName, new ArraySet<>(tags), + UserHandle.getAppId(resolvedService.serviceInfo.applicationInfo.uid), + packageTagsList, mActivityRecognitionTags); } } } - private static void updateAllowListedTagsForPackageLocked(int uid, String packageName, - Set<String> allowListedTags, ConcurrentHashMap<Integer, ArrayMap<String, - ArraySet<String>>> datastore) { - final int appId = UserHandle.getAppId(uid); - // We make a copy of the per UID state to limit our mutation to one - // operation in the underlying concurrent data structure. - ArrayMap<String, ArraySet<String>> appIdTags = datastore.get(appId); - if (appIdTags != null) { - appIdTags = new ArrayMap<>(appIdTags); - } - - ArraySet<String> packageTags = (appIdTags != null) ? appIdTags.get(packageName) : null; - if (packageTags != null) { - packageTags = new ArraySet<>(packageTags); - } - - if (allowListedTags != null && !allowListedTags.isEmpty()) { - if (packageTags != null) { - packageTags.clear(); - packageTags.addAll(allowListedTags); - } else { - packageTags = new ArraySet<>(allowListedTags); - } - if (appIdTags == null) { - appIdTags = new ArrayMap<>(); - } - - // Remove any invalid tags - boolean nullRemoved = packageTags.remove(null); - boolean nullStrRemoved = packageTags.remove("null"); - boolean emptyRemoved = packageTags.remove(""); - if (nullRemoved || nullStrRemoved || emptyRemoved) { - Log.e(LOG_TAG, "Attempted to add invalid source attribution tag, removed " - + "null: " + nullRemoved + " removed \"null\": " + nullStrRemoved - + " removed empty string: " + emptyRemoved); - } - - appIdTags.put(packageName, packageTags); - datastore.put(appId, appIdTags); - } else if (appIdTags != null) { - appIdTags.remove(packageName); - if (!appIdTags.isEmpty()) { - datastore.put(appId, appIdTags); - } else { - datastore.remove(appId); - } - } + private static void updateAllowListedTagsForPackageLocked(int appId, + PackageTagsList packageTagsList, + ConcurrentHashMap<Integer, PackageTagsList> datastore) { + datastore.put(appId, packageTagsList); } private static boolean isDatasourceAttributionTag(int uid, @NonNull String packageName, - @NonNull String attributionTag, @NonNull Map<Integer, ArrayMap<String, - ArraySet<String>>> mappedOps) { + @NonNull String attributionTag, @NonNull Map<Integer, PackageTagsList> mappedOps) { // Only a single lookup from the underlying concurrent data structure - final int appId = UserHandle.getAppId(uid); - final ArrayMap<String, ArraySet<String>> appIdTags = mappedOps.get(appId); - if (appIdTags != null) { - final ArraySet<String> packageTags = appIdTags.get(packageName); - if (packageTags != null && packageTags.contains(attributionTag)) { - if (packageName.equals("com.google.android.gms") - && !sExpectedTags.contains(attributionTag)) { - Log.i("AppOpsDebugRemapping", packageName + " tag " - + attributionTag + " in " + packageTags); - } - return true; - } - if (packageName.equals("com.google.android.gms") - && sExpectedTags.contains(attributionTag)) { - Log.i("AppOpsDebugRemapping", packageName + " tag " + attributionTag - + " NOT in " + packageTags); - } - } else { - if (packageName.equals("com.google.android.gms")) { - Log.i("AppOpsDebugRemapping", "no package tags for uid " + uid - + " package " + packageName); - } - - } - return false; + final PackageTagsList appIdTags = mappedOps.get(UserHandle.getAppId(uid)); + return appIdTags != null && appIdTags.contains(packageName, attributionTag); } private static int resolveLocationOp(int code) { diff --git a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java index e1e6195ad260..f653e4b26438 100644 --- a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java +++ b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java @@ -30,6 +30,9 @@ public final class ProcfsMemoryUtil { "RssAnon:", "VmSwap:" }; + private static final String[] VMSTAT_KEYS = new String[] { + "oom_kill" + }; private ProcfsMemoryUtil() {} @@ -99,4 +102,22 @@ public final class ProcfsMemoryUtil { public int anonRssInKilobytes; public int swapInKilobytes; } + + /** Reads and parses selected entries of /proc/vmstat. */ + @Nullable + static VmStat readVmStat() { + long[] vmstat = new long[VMSTAT_KEYS.length]; + vmstat[0] = -1; + Process.readProcLines("/proc/vmstat", VMSTAT_KEYS, vmstat); + if (vmstat[0] == -1) { + return null; + } + VmStat result = new VmStat(); + result.oomKillCount = (int) vmstat[0]; + return result; + } + + static final class VmStat { + public int oomKillCount; + } } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index dc868b325900..cd0ce2bd46a7 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -544,6 +544,8 @@ public class StatsPullAtomService extends SystemService { return pullProcessDmabufMemory(atomTag, data); case FrameworkStatsLog.SYSTEM_MEMORY: return pullSystemMemory(atomTag, data); + case FrameworkStatsLog.VMSTAT: + return pullVmStat(atomTag, data); case FrameworkStatsLog.TEMPERATURE: synchronized (mTemperatureLock) { return pullTemperatureLocked(atomTag, data); @@ -842,6 +844,7 @@ public class StatsPullAtomService extends SystemService { registerProcessSystemIonHeapSize(); registerSystemMemory(); registerProcessDmabufMemory(); + registerVmStat(); registerTemperature(); registerCoolingDevice(); registerBinderCallsStats(); @@ -2273,6 +2276,27 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } + private void registerVmStat() { + int tagId = FrameworkStatsLog.VMSTAT; + mStatsManager.setPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + DIRECT_EXECUTOR, + mStatsCallbackImpl + ); + } + + int pullVmStat(int atomTag, List<StatsEvent> pulledData) { + ProcfsMemoryUtil.VmStat vmStat = ProcfsMemoryUtil.readVmStat(); + if (vmStat != null) { + pulledData.add( + FrameworkStatsLog.buildStatsEvent( + atomTag, + vmStat.oomKillCount)); + } + return StatsManager.PULL_SUCCESS; + } + private void registerTemperature() { int tagId = FrameworkStatsLog.TEMPERATURE; mStatsManager.setPullAtomCallback( diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 71e31c3a10df..ac0665a37c0b 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -703,7 +703,7 @@ final class AccessibilityController { Slog.i(LOG_TAG, "Rotation: " + Surface.rotationToString(rotation) + " displayId: " + displayContent.getDisplayId()); } - mMagnifedViewport.onRotationChanged(displayContent.getPendingTransaction()); + mMagnifedViewport.onRotationChanged(); mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED); } @@ -858,7 +858,7 @@ final class AccessibilityController { private final RectF mTempRectF = new RectF(); - private final Point mTempPoint = new Point(); + private final Point mScreenSize = new Point(); private final Matrix mTempMatrix = new Matrix(); @@ -887,8 +887,8 @@ final class AccessibilityController { if (mDisplayContext.getResources().getConfiguration().isScreenRound()) { mCircularPath = new Path(); - mDisplay.getRealSize(mTempPoint); - final int centerXY = mTempPoint.x / 2; + mDisplay.getRealSize(mScreenSize); + final int centerXY = mScreenSize.x / 2; mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW); } else { mCircularPath = null; @@ -917,9 +917,9 @@ final class AccessibilityController { } void recomputeBounds() { - mDisplay.getRealSize(mTempPoint); - final int screenWidth = mTempPoint.x; - final int screenHeight = mTempPoint.y; + mDisplay.getRealSize(mScreenSize); + final int screenWidth = mScreenSize.x; + final int screenHeight = mScreenSize.y; mMagnificationRegion.set(0, 0, 0, 0); final Region availableBounds = mTempRegion1; @@ -1052,7 +1052,7 @@ final class AccessibilityController { || windowType == TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; } - void onRotationChanged(SurfaceControl.Transaction t) { + void onRotationChanged() { // If we are showing the magnification border, hide it immediately so // the user does not see strange artifacts during rotation. The screenshot // used for rotation already has the border. After the rotation is complete @@ -1066,7 +1066,7 @@ final class AccessibilityController { mHandler.sendMessageDelayed(message, delay); } recomputeBounds(); - mWindow.updateSize(t); + mWindow.updateSize(); } void setMagnifiedRegionBorderShown(boolean shown, boolean animate) { @@ -1148,9 +1148,9 @@ final class AccessibilityController { /* ignore */ } mSurfaceControl = surfaceControl; - mDisplay.getRealSize(mTempPoint); + mDisplay.getRealSize(mScreenSize); mBlastBufferQueue = new BLASTBufferQueue(SURFACE_TITLE, mSurfaceControl, - mTempPoint.x, mTempPoint.y, PixelFormat.RGBA_8888); + mScreenSize.x, mScreenSize.y, PixelFormat.RGBA_8888); final SurfaceControl.Transaction t = mService.mTransactionFactory.get(); final int layer = @@ -1224,10 +1224,11 @@ final class AccessibilityController { } } - void updateSize(SurfaceControl.Transaction t) { + void updateSize() { synchronized (mService.mGlobalLock) { - mDisplay.getRealSize(mTempPoint); - t.setBufferSize(mSurfaceControl, mTempPoint.x, mTempPoint.y); + mDisplay.getRealSize(mScreenSize); + mBlastBufferQueue.update(mSurfaceControl, mScreenSize.x, mScreenSize.y, + PixelFormat.RGBA_8888); invalidate(mDirtyRect); } } @@ -1296,8 +1297,8 @@ final class AccessibilityController { pw.println(prefix + " mBounds= " + mBounds + " mDirtyRect= " + mDirtyRect - + " mWidth= " + mSurfaceControl.getWidth() - + " mHeight= " + mSurfaceControl.getHeight()); + + " mWidth= " + mScreenSize.x + + " mHeight= " + mScreenSize.y); } private final class AnimationController extends Handler { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 45da45aafaba..8514f3599832 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -309,6 +309,7 @@ import android.view.animation.Animation; import android.window.IRemoteTransition; import android.window.SizeConfigurationBuckets; import android.window.SplashScreen; +import android.window.SplashScreenView; import android.window.SplashScreenView.SplashScreenViewParcelable; import android.window.TaskSnapshot; import android.window.WindowContainerToken; @@ -716,25 +717,29 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean startingMoved; boolean mHandleExitSplashScreen; - @TransferSplashScreenState int mTransferringSplashScreenState = - TRANSFER_SPLASH_SCREEN_IDLE; + @TransferSplashScreenState + int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE; - // Idle, can be triggered to do transfer if needed. + /** Idle, can be triggered to do transfer if needed. */ static final int TRANSFER_SPLASH_SCREEN_IDLE = 0; - // requesting a copy from shell. + + /** Requesting a copy from shell. */ static final int TRANSFER_SPLASH_SCREEN_COPYING = 1; - // attach the splash screen view to activity. + + /** Attach the splash screen view to activity. */ static final int TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT = 2; - // client has taken over splash screen view. + + /** Client has taken over splash screen view. */ static final int TRANSFER_SPLASH_SCREEN_FINISH = 3; - @IntDef(prefix = { "TRANSFER_SPLASH_SCREEN_" }, value = { + @IntDef(prefix = {"TRANSFER_SPLASH_SCREEN_"}, value = { TRANSFER_SPLASH_SCREEN_IDLE, TRANSFER_SPLASH_SCREEN_COPYING, TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT, TRANSFER_SPLASH_SCREEN_FINISH, }) - @interface TransferSplashScreenState {} + @interface TransferSplashScreenState { + } // How long we wait until giving up transfer splash screen. private static final int TRANSFER_SPLASH_SCREEN_TIMEOUT = 2000; @@ -2196,6 +2201,23 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A removeStartingWindowAnimation(false /* prepareAnimation */); } + /** + * Notify the shell ({@link com.android.wm.shell.ShellTaskOrganizer} it should clean up any + * remaining reference to this {@link ActivityRecord}'s splash screen. + * @see com.android.wm.shell.ShellTaskOrganizer#onAppSplashScreenViewRemoved(int) + * @see SplashScreenView#remove() + */ + void cleanUpSplashScreen() { + // We only clean up the splash screen if we were supposed to handle it. If it was + // transferred to another activity, the next one will handle the clean up. + if (mHandleExitSplashScreen && !startingMoved + && (mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH + || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_IDLE)) { + ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Cleaning splash screen token=%s", this); + mAtmService.mTaskOrganizerController.onAppSplashScreenViewRemoved(getTask()); + } + } + void removeStartingWindow() { removeStartingWindowAnimation(true /* prepareAnimation */); } @@ -3343,6 +3365,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A task.cleanUpActivityReferences(this); clearLastParentBeforePip(); + // Clean up the splash screen if it was still displayed. + cleanUpSplashScreen(); + deferRelaunchUntilPaused = false; frozenBeforeDestroy = false; @@ -7504,10 +7529,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (getUid() == SYSTEM_UID) { return false; } - // Do not sandbox to activity window bounds if the feature is disabled. - if (mDisplayContent != null && !mDisplayContent.sandboxDisplayApis()) { - return false; - } // Never apply sandboxing to an app that should be explicitly excluded from the config. if (info != null && info.neverSandboxDisplayApis()) { return false; diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index eaebb6f1ce74..4acadb21b5e3 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -623,7 +623,11 @@ public class AppTransitionController { siblings.add(current); boolean canPromote = true; - if (parent == null || !parent.canCreateRemoteAnimationTarget()) { + if (parent == null || !parent.canCreateRemoteAnimationTarget() + // We cannot promote the animation on Task's parent when the task is in + // clearing task in case the animating get stuck when performing the opening + // task that behind it. + || (current.asTask() != null && current.asTask().mInRemoveTask)) { canPromote = false; } else { // In case a descendant of the parent belongs to the other group, we cannot promote diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 81992d8934ed..e0bae9d8de32 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -358,13 +358,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp boolean mIsSizeForced = false; /** - * Overridden display size and metrics to activity window bounds. Set via - * "adb shell wm set-sandbox-display-apis". Default to true, since only disable for debugging. - * @see WindowManagerService#setSandboxDisplayApis(int, boolean) - */ - private boolean mSandboxDisplayApis = true; - - /** * Overridden display density for current user. Initialized with {@link #mInitialDisplayDensity} * but can be set from Settings or via shell command "adb shell wm density". * @see WindowManagerService#setForcedDisplayDensityForUser(int, int, int) @@ -5810,21 +5803,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return true; } - /** - * Sets if Display APIs should be sandboxed to the activity window bounds. - */ - void setSandboxDisplayApis(boolean sandboxDisplayApis) { - mSandboxDisplayApis = sandboxDisplayApis; - } - - /** - * Returns {@code true} is Display APIs should be sandboxed to the activity window bounds, - * {@code false} otherwise. Default to true, unless set for debugging purposes. - */ - boolean sandboxDisplayApis() { - return mSandboxDisplayApis; - } - /** The entry for proceeding to handle {@link #mFixedRotationLaunchingApp}. */ class FixedRotationTransitionListener extends WindowManagerInternal.AppTransitionListener { diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 0112f797d937..f8238c1d154a 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -48,6 +48,7 @@ import android.os.Trace; import android.util.Slog; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; +import android.view.Display; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.server.inputmethod.InputMethodManagerInternal; @@ -163,16 +164,27 @@ class KeyguardController { aodShowing ? 1 : 0, mKeyguardGoingAway ? 1 : 0, "setKeyguardShown"); + + // Update the task snapshot if the screen will not be turned off. To make sure that the + // unlocking animation can animate consistent content. The conditions are: + // - Either AOD or keyguard changes to be showing. So if the states change individually, + // the later one can be skipped to avoid taking snapshot again. While it still accepts + // if both of them change to show at the same time. + // - Keyguard was not going away. Because if it was, the closing transition is able to + // handle the snapshot. + // - The display state is ON. Because if AOD is not on or pulsing, the display state will + // be OFF or DOZE (the path of screen off may have handled it). + if (((aodShowing ^ keyguardShowing) || (aodShowing && aodChanged && keyguardChanged)) + && !mKeyguardGoingAway && Display.isOnState( + mRootWindowContainer.getDefaultDisplay().getDisplayInfo().state)) { + mWindowManager.mTaskSnapshotController.snapshotForSleeping(DEFAULT_DISPLAY); + } + mKeyguardShowing = keyguardShowing; mAodShowing = aodShowing; if (aodChanged) { // Ensure the new state takes effect. mWindowManager.mWindowPlacerLocked.performSurfacePlacement(); - // If the device can enter AOD and keyguard at the same time, the screen will not be - // turned off, so the snapshot needs to be refreshed when these states are changed. - if (aodShowing && keyguardShowing && keyguardChanged) { - mWindowManager.mTaskSnapshotController.snapshotForSleeping(DEFAULT_DISPLAY); - } } if (keyguardChanged) { diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index eb7087cbc722..7174e68b06f4 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -21,6 +21,7 @@ import android.content.Context; import android.graphics.Color; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -104,20 +105,12 @@ final class LetterboxConfiguration { * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and * the framework implementation will be used to determine the aspect ratio. */ + @VisibleForTesting void setFixedOrientationLetterboxAspectRatio(float aspectRatio) { mFixedOrientationLetterboxAspectRatio = aspectRatio; } /** - * Resets the aspect ratio of letterbox for fixed orientation to {@link - * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}. - */ - void resetFixedOrientationLetterboxAspectRatio() { - mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat( - com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio); - } - - /** * Gets the aspect ratio of letterbox for fixed orientation. */ float getFixedOrientationLetterboxAspectRatio() { @@ -125,25 +118,6 @@ final class LetterboxConfiguration { } /** - * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0, - * both it and a value of {@link - * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and - * and corners of the activity won't be rounded. - */ - void setLetterboxActivityCornersRadius(int cornersRadius) { - mLetterboxActivityCornersRadius = cornersRadius; - } - - /** - * Resets corners raidus for activities presented in the letterbox mode to {@link - * com.android.internal.R.integer.config_letterboxActivityCornersRadius}. - */ - void resetLetterboxActivityCornersRadius() { - mLetterboxActivityCornersRadius = mContext.getResources().getInteger( - com.android.internal.R.integer.config_letterboxActivityCornersRadius); - } - - /** * Whether corners of letterboxed activities are rounded. */ boolean isLetterboxActivityCornersRounded() { @@ -166,25 +140,6 @@ final class LetterboxConfiguration { return mLetterboxBackgroundColor; } - - /** - * Sets color of letterbox background which is used when {@link - * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as - * fallback for other backfround types. - */ - void setLetterboxBackgroundColor(Color color) { - mLetterboxBackgroundColor = color; - } - - /** - * Resets color of letterbox background to {@link - * com.android.internal.R.color.config_letterboxBackgroundColor}. - */ - void resetLetterboxBackgroundColor() { - mLetterboxBackgroundColor = Color.valueOf(mContext.getResources().getColor( - com.android.internal.R.color.config_letterboxBackgroundColor)); - } - /** * Gets {@link LetterboxBackgroundType} specified in {@link * com.android.internal.R.integer.config_letterboxBackgroundType} or over via ADB command. @@ -194,19 +149,6 @@ final class LetterboxConfiguration { return mLetterboxBackgroundType; } - /** Sets letterbox background type. */ - void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) { - mLetterboxBackgroundType = backgroundType; - } - - /** - * Resets cletterbox background type to {@link - * com.android.internal.R.integer.config_letterboxBackgroundType}. - */ - void resetLetterboxBackgroundType() { - mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext); - } - /** Returns a string representing the given {@link LetterboxBackgroundType}. */ static String letterboxBackgroundTypeToString( @LetterboxBackgroundType int backgroundType) { @@ -236,27 +178,6 @@ final class LetterboxConfiguration { } /** - * Overrides alpha of a black scrim shown over wallpaper for {@link - * #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link mLetterboxBackgroundType}. - * - * <p>If given value is < 0 or >= 1, both it and a value of {@link - * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha} are ignored - * and 0.0 (transparent) is instead. - */ - void setLetterboxBackgroundWallpaperDarkScrimAlpha(float alpha) { - mLetterboxBackgroundWallpaperDarkScrimAlpha = alpha; - } - - /** - * Resets alpha of a black scrim shown over wallpaper letterbox background to {@link - * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha}. - */ - void resetLetterboxBackgroundWallpaperDarkScrimAlpha() { - mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat( - com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha); - } - - /** * Gets alpha of a black scrim shown over wallpaper letterbox background. */ float getLetterboxBackgroundWallpaperDarkScrimAlpha() { @@ -264,28 +185,6 @@ final class LetterboxConfiguration { } /** - * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in - * {@link mLetterboxBackgroundType}. - * - * <p> If given value <= 0, both it and a value of {@link - * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius} are ignored - * and 0 is used instead. - */ - void setLetterboxBackgroundWallpaperBlurRadius(int radius) { - mLetterboxBackgroundWallpaperBlurRadius = radius; - } - - /** - * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link - * mLetterboxBackgroundType} to {@link - * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius}. - */ - void resetLetterboxBackgroundWallpaperBlurRadius() { - mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius); - } - - /** * Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link * mLetterboxBackgroundType}. */ @@ -312,17 +211,9 @@ final class LetterboxConfiguration { * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and * central position (0.5) is used. */ + @VisibleForTesting void setLetterboxHorizontalPositionMultiplier(float multiplier) { mLetterboxHorizontalPositionMultiplier = multiplier; } - /** - * Resets horizontal position of a center of the letterboxed app window to {@link - * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}. - */ - void resetLetterboxHorizontalPositionMultiplier() { - mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat( - com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier); - } - } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 7af8d8bb5200..565f99f80890 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -6689,24 +6689,26 @@ class Task extends WindowContainer<WindowContainer> { } } - // TODO(185200798): Persist theme name instead of theme if - int splashScreenThemeResId = options != null - ? options.getSplashScreenThemeResId() : 0; - - // User can override the splashscreen theme. The theme name is used to persist - // the setting, so if no theme is set in the ActivityOptions, we check if has - // been persisted here. - if (splashScreenThemeResId == 0) { + // Find the splash screen theme. User can override the persisted theme by + // ActivityOptions. + String splashScreenThemeResName = options != null + ? options.getSplashScreenThemeResName() : null; + if (splashScreenThemeResName == null || splashScreenThemeResName.isEmpty()) { try { - String themeName = mAtmService.getPackageManager() + splashScreenThemeResName = mAtmService.getPackageManager() .getSplashScreenTheme(r.packageName, r.mUserId); - if (themeName != null) { - Context packageContext = mAtmService.mContext - .createPackageContext(r.packageName, 0); - splashScreenThemeResId = packageContext.getResources() - .getIdentifier(themeName, null, null); - } - } catch (RemoteException | PackageManager.NameNotFoundException + } catch (RemoteException ignore) { + // Just use the default theme + } + } + int splashScreenThemeResId = 0; + if (splashScreenThemeResName != null && !splashScreenThemeResName.isEmpty()) { + try { + final Context packageContext = mAtmService.mContext + .createPackageContext(r.packageName, 0); + splashScreenThemeResId = packageContext.getResources() + .getIdentifier(splashScreenThemeResName, null, null); + } catch (PackageManager.NameNotFoundException | Resources.NotFoundException ignore) { // Just use the default theme } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index f23028f6f67a..3a2ca80f2e12 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -43,6 +43,7 @@ import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl; import android.window.ITaskOrganizer; import android.window.ITaskOrganizerController; +import android.window.SplashScreenView; import android.window.StartingWindowInfo; import android.window.TaskAppearedInfo; import android.window.TaskSnapshot; @@ -215,6 +216,14 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } } + void onAppSplashScreenViewRemoved(Task task) { + try { + mTaskOrganizer.onAppSplashScreenViewRemoved(task.mTaskId); + } catch (RemoteException e) { + Slog.e(TAG, "Exception sending onAppSplashScreenViewRemoved callback", e); + } + } + SurfaceControl prepareLeash(Task task, String reason) { return new SurfaceControl(task.getSurfaceControl(), reason); } @@ -314,6 +323,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mOrganizer.copySplashScreenView(t); } + public void onAppSplashScreenViewRemoved(Task t) { + mOrganizer.onAppSplashScreenViewRemoved(t); + } + /** * Register this task with this state, but doesn't trigger the task appeared callback to * the organizer. @@ -566,6 +579,22 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return true; } + /** + * Notify the shell ({@link com.android.wm.shell.ShellTaskOrganizer} that the client has + * removed the splash screen view. + * @see com.android.wm.shell.ShellTaskOrganizer#onAppSplashScreenViewRemoved(int) + * @see SplashScreenView#remove() + */ + public void onAppSplashScreenViewRemoved(Task task) { + final Task rootTask = task.getRootTask(); + if (rootTask == null || rootTask.mTaskOrganizer == null) { + return; + } + final TaskOrganizerState state = + mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder()); + state.onAppSplashScreenViewRemoved(task); + } + void onTaskAppeared(ITaskOrganizer organizer, Task task) { final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); if (state != null && state.addTask(task)) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 6d51849ea513..e9dd521670a9 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5391,25 +5391,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - void setSandboxDisplayApis(int displayId, boolean sandboxDisplayApis) { - if (mContext.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Must hold permission " + WRITE_SECURE_SETTINGS); - } - - final long ident = Binder.clearCallingIdentity(); - try { - synchronized (mGlobalLock) { - final DisplayContent displayContent = mRoot.getDisplayContent(displayId); - if (displayContent != null) { - displayContent.setSandboxDisplayApis(sandboxDisplayApis); - } - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - /** The global settings only apply to default display. */ private boolean applyForcedPropertiesForDefaultDisplay() { boolean changed = false; diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index d59654949a27..a94fd074ff2e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -19,12 +19,6 @@ package com.android.server.wm; import static android.os.Build.IS_USER; import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; - -import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.os.ParcelFileDescriptor; @@ -42,7 +36,6 @@ import com.android.internal.os.ByteTransferPipe; import com.android.internal.protolog.ProtoLogImpl; import com.android.server.LocalServices; import com.android.server.statusbar.StatusBarManagerInternal; -import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; import java.io.IOException; import java.io.PrintWriter; @@ -65,12 +58,10 @@ public class WindowManagerShellCommand extends ShellCommand { // Internal service impl -- must perform security checks before touching. private final WindowManagerService mInternal; - private final LetterboxConfiguration mLetterboxConfiguration; public WindowManagerShellCommand(WindowManagerService service) { mInterface = service; mInternal = service; - mLetterboxConfiguration = service.mLetterboxConfiguration; } @Override @@ -122,14 +113,6 @@ public class WindowManagerShellCommand extends ShellCommand { return runGetIgnoreOrientationRequest(pw); case "dump-visible-window-views": return runDumpVisibleWindowViews(pw); - case "set-letterbox-style": - return runSetLetterboxStyle(pw); - case "get-letterbox-style": - return runGetLetterboxStyle(pw); - case "reset-letterbox-style": - return runResetLetterboxStyle(pw); - case "set-sandbox-display-apis": - return runSandboxDisplayApis(pw); case "set-multi-window-config": return runSetMultiWindowConfig(); case "get-multi-window-config": @@ -348,37 +331,6 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } - /** - * Override display size and metrics to reflect the DisplayArea of the calling activity. - */ - private int runSandboxDisplayApis(PrintWriter pw) throws RemoteException { - int displayId = Display.DEFAULT_DISPLAY; - String arg = getNextArgRequired(); - if ("-d".equals(arg)) { - displayId = Integer.parseInt(getNextArgRequired()); - arg = getNextArgRequired(); - } - - final boolean sandboxDisplayApis; - switch (arg) { - case "true": - case "1": - sandboxDisplayApis = true; - break; - case "false": - case "0": - sandboxDisplayApis = false; - break; - default: - getErrPrintWriter().println("Error: expecting true, 1, false, 0, but we " - + "get " + arg); - return -1; - } - - mInternal.setSandboxDisplayApis(displayId, sandboxDisplayApis); - return 0; - } - private int runDismissKeyguard(PrintWriter pw) throws RemoteException { mInterface.dismissKeyguard(null /* callback */, null /* message */); return 0; @@ -596,231 +548,6 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } - private int runSetFixedOrientationLetterboxAspectRatio(PrintWriter pw) throws RemoteException { - final float aspectRatio; - try { - String arg = getNextArgRequired(); - aspectRatio = Float.parseFloat(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: bad aspect ratio format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'reset' or aspect ratio should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(aspectRatio); - } - return 0; - } - - private int runSetLetterboxActivityCornersRadius(PrintWriter pw) throws RemoteException { - final int cornersRadius; - try { - String arg = getNextArgRequired(); - cornersRadius = Integer.parseInt(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: bad corners radius format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'reset' or corners radius should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxActivityCornersRadius(cornersRadius); - } - return 0; - } - - private int runSetLetterboxBackgroundType(PrintWriter pw) throws RemoteException { - @LetterboxBackgroundType final int backgroundType; - try { - String arg = getNextArgRequired(); - switch (arg) { - case "solid_color": - backgroundType = LETTERBOX_BACKGROUND_SOLID_COLOR; - break; - case "app_color_background": - backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; - break; - case "app_color_background_floating": - backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; - break; - case "wallpaper": - backgroundType = LETTERBOX_BACKGROUND_WALLPAPER; - break; - default: - getErrPrintWriter().println( - "Error: 'reset', 'solid_color', 'app_color_background' or " - + "'wallpaper' should be provided as an argument"); - return -1; - } - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'reset', 'solid_color', 'app_color_background' or " - + "'wallpaper' should be provided as an argument" + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundType(backgroundType); - } - return 0; - } - - private int runSetLetterboxBackgroundColor(PrintWriter pw) throws RemoteException { - final Color color; - try { - String arg = getNextArgRequired(); - color = Color.valueOf(Color.parseColor(arg)); - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'reset' or color in #RRGGBB format should be provided as " - + "an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundColor(color); - } - return 0; - } - - private int runSetLetterboxBackgroundWallpaperBlurRadius(PrintWriter pw) - throws RemoteException { - final int radius; - try { - String arg = getNextArgRequired(); - radius = Integer.parseInt(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: blur radius format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'reset' or blur radius should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadius(radius); - } - return 0; - } - - private int runSetLetterboxBackgroundWallpaperDarkScrimAlpha(PrintWriter pw) - throws RemoteException { - final float alpha; - try { - String arg = getNextArgRequired(); - alpha = Float.parseFloat(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: bad alpha format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'reset' or alpha should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundWallpaperDarkScrimAlpha(alpha); - } - return 0; - } - - private int runSeLetterboxHorizontalPositionMultiplier(PrintWriter pw) throws RemoteException { - final float multiplier; - try { - String arg = getNextArgRequired(); - multiplier = Float.parseFloat(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: bad multiplier format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'reset' or multiplier should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier); - } - return 0; - } - - private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException { - if (peekNextArg() == null) { - getErrPrintWriter().println("Error: No arguments provided."); - } - while (peekNextArg() != null) { - String arg = getNextArg(); - switch (arg) { - case "--aspectRatio": - runSetFixedOrientationLetterboxAspectRatio(pw); - break; - case "--cornerRadius": - runSetLetterboxActivityCornersRadius(pw); - break; - case "--backgroundType": - runSetLetterboxBackgroundType(pw); - break; - case "--backgroundColor": - runSetLetterboxBackgroundColor(pw); - break; - case "--wallpaperBlurRadius": - runSetLetterboxBackgroundWallpaperBlurRadius(pw); - break; - case "--wallpaperDarkScrimAlpha": - runSetLetterboxBackgroundWallpaperDarkScrimAlpha(pw); - break; - case "--horizontalPositionMultiplier": - runSeLetterboxHorizontalPositionMultiplier(pw); - break; - default: - getErrPrintWriter().println( - "Error: Unrecognized letterbox style option: " + arg); - return -1; - } - } - return 0; - } - - private int runResetLetterboxStyle(PrintWriter pw) throws RemoteException { - if (peekNextArg() == null) { - resetLetterboxStyle(); - } - synchronized (mInternal.mGlobalLock) { - while (peekNextArg() != null) { - String arg = getNextArg(); - switch (arg) { - case "aspectRatio": - mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio(); - break; - case "cornerRadius": - mLetterboxConfiguration.resetLetterboxActivityCornersRadius(); - break; - case "backgroundType": - mLetterboxConfiguration.resetLetterboxBackgroundType(); - break; - case "backgroundColor": - mLetterboxConfiguration.resetLetterboxBackgroundColor(); - break; - case "wallpaperBlurRadius": - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius(); - break; - case "wallpaperDarkScrimAlpha": - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); - break; - case "horizontalPositionMultiplier": - mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier(); - break; - default: - getErrPrintWriter().println( - "Error: Unrecognized letterbox style option: " + arg); - return -1; - } - } - } - return 0; - } - private int runSetMultiWindowConfig() { if (peekNextArg() == null) { getErrPrintWriter().println("Error: No arguments provided."); @@ -895,40 +622,6 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } - private void resetLetterboxStyle() { - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio(); - mLetterboxConfiguration.resetLetterboxActivityCornersRadius(); - mLetterboxConfiguration.resetLetterboxBackgroundType(); - mLetterboxConfiguration.resetLetterboxBackgroundColor(); - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius(); - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); - mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier(); - } - } - - private int runGetLetterboxStyle(PrintWriter pw) throws RemoteException { - synchronized (mInternal.mGlobalLock) { - pw.println("Corner radius: " - + mLetterboxConfiguration.getLetterboxActivityCornersRadius()); - pw.println("Horizontal position multiplier: " - + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier()); - pw.println("Aspect ratio: " - + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio()); - - pw.println("Background type: " - + LetterboxConfiguration.letterboxBackgroundTypeToString( - mLetterboxConfiguration.getLetterboxBackgroundType())); - pw.println(" Background color: " + Integer.toHexString( - mLetterboxConfiguration.getLetterboxBackgroundColor().toArgb())); - pw.println(" Wallpaper blur radius: " - + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius()); - pw.println(" Wallpaper dark scrim alpha: " - + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha()); - } - return 0; - } - private int runReset(PrintWriter pw) throws RemoteException { int displayId = getDisplayId(getNextArg()); @@ -953,12 +646,6 @@ public class WindowManagerShellCommand extends ShellCommand { // set-ignore-orientation-request mInterface.setIgnoreOrientationRequest(displayId, false /* ignoreOrientationRequest */); - // set-letterbox-style - resetLetterboxStyle(); - - // set-sandbox-display-apis - mInternal.setSandboxDisplayApis(displayId, /* sandboxDisplayApis= */ true); - // set-multi-window-config runResetMultiWindowConfig(); @@ -993,12 +680,7 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" set-ignore-orientation-request [-d DISPLAY_ID] [true|1|false|0]"); pw.println(" get-ignore-orientation-request [-d DISPLAY_ID] "); pw.println(" If app requested orientation should be ignored."); - pw.println(" set-sandbox-display-apis [true|1|false|0]"); - pw.println(" Sets override of Display APIs getRealSize / getRealMetrics to reflect "); - pw.println(" DisplayArea of the activity, or the window bounds if in letterbox or"); - pw.println(" Size Compat Mode."); - printLetterboxHelp(pw); printMultiWindowConfigHelp(pw); pw.println(" reset [-d DISPLAY_ID]"); @@ -1011,49 +693,6 @@ public class WindowManagerShellCommand extends ShellCommand { } } - private void printLetterboxHelp(PrintWriter pw) { - pw.println(" set-letterbox-style"); - pw.println(" Sets letterbox style using the following options:"); - pw.println(" --aspectRatio aspectRatio"); - pw.println(" Aspect ratio of letterbox for fixed orientation. If aspectRatio <= " - + LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO); - pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will"); - pw.println(" be ignored and framework implementation will determine aspect ratio."); - pw.println(" --cornerRadius radius"); - pw.println(" Corners radius for activities in the letterbox mode. If radius < 0,"); - pw.println(" both it and R.integer.config_letterboxActivityCornersRadius will be"); - pw.println(" ignored and corners of the activity won't be rounded."); - pw.println(" --backgroundType [reset|solid_color|app_color_background"); - pw.println(" |app_color_background_floating|wallpaper]"); - pw.println(" Type of background used in the letterbox mode."); - pw.println(" --backgroundColor color"); - pw.println(" Color of letterbox which is be used when letterbox background type"); - pw.println(" is 'solid-color'. Use (set)get-letterbox-style to check and control"); - pw.println(" letterbox background type. See Color#parseColor for allowed color"); - pw.println(" formats (#RRGGBB and some colors by name, e.g. magenta or olive)."); - pw.println(" --wallpaperBlurRadius radius"); - pw.println(" Blur radius for 'wallpaper' letterbox background. If radius <= 0"); - pw.println(" both it and R.dimen.config_letterboxBackgroundWallpaperBlurRadius"); - pw.println(" are ignored and 0 is used."); - pw.println(" --wallpaperDarkScrimAlpha alpha"); - pw.println(" Alpha of a black translucent scrim shown over 'wallpaper'"); - pw.println(" letterbox background. If alpha < 0 or >= 1 both it and"); - pw.println(" R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha are ignored"); - pw.println(" and 0.0 (transparent) is used instead."); - pw.println(" --horizontalPositionMultiplier multiplier"); - pw.println(" Horizontal position of app window center. If multiplier < 0 or > 1,"); - pw.println(" both it and R.dimen.config_letterboxHorizontalPositionMultiplier"); - pw.println(" are ignored and central position (0.5) is used."); - pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType"); - pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha"); - pw.println(" |horizontalPositionMultiplier]"); - pw.println(" Resets overrides to default values for specified properties separated"); - pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'."); - pw.println(" If no arguments provided, all values will be reset."); - pw.println(" get-letterbox-style"); - pw.println(" Prints letterbox style configuration."); - } - private void printMultiWindowConfigHelp(PrintWriter pw) { pw.println(" set-multi-window-config"); pw.println(" Sets options to determine if activity should be shown in multi window:"); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index fa24e5238820..2439760e3f67 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -169,6 +169,7 @@ import android.app.admin.DevicePolicyManager.OperationSafetyReason; import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason; import android.app.admin.DevicePolicyManagerInternal; +import android.app.admin.DevicePolicyManagerLiteInternal; import android.app.admin.DevicePolicySafetyChecker; import android.app.admin.DeviceStateCache; import android.app.admin.FactoryResetProtectionPolicy; @@ -1748,6 +1749,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mTransferOwnershipMetadataManager = mInjector.newTransferOwnershipMetadataManager(); mBugreportCollectionManager = new RemoteBugreportManager(this, mInjector); + // "Lite" interface is available even when the device doesn't have the feature + LocalServices.addService(DevicePolicyManagerLiteInternal.class, mLocalService); if (!mHasFeature) { // Skip the rest of the initialization mSetupContentObserver = null; @@ -8027,6 +8030,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { inForeground = true; receiverComponent = resolveDelegateReceiver( DELEGATION_SECURITY_LOGGING, action, userId); + // STOPSHIP(b/185004808): remove excessive log. + Slogf.d(LOG_TAG, "Delegate for security logs broadcast: " + receiverComponent); } if (receiverComponent == null) { receiverComponent = getOwnerComponent(userId); @@ -12612,7 +12617,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @VisibleForTesting - final class LocalService extends DevicePolicyManagerInternal { + final class LocalService extends DevicePolicyManagerInternal + implements DevicePolicyManagerLiteInternal { private List<OnCrossProfileWidgetProvidersChangeListener> mWidgetProviderListeners; @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java index 86437a27a64d..1cbc634b7152 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java @@ -21,7 +21,7 @@ import static android.app.admin.DevicePolicyManager.operationToString; import android.app.admin.DevicePolicyManager.DevicePolicyOperation; import android.app.admin.DevicePolicyManager.OperationSafetyReason; -import android.app.admin.DevicePolicyManagerInternal; +import android.app.admin.DevicePolicyManagerLiteInternal; import android.app.admin.DevicePolicySafetyChecker; import android.os.Handler; import android.os.Looper; @@ -80,8 +80,8 @@ final class OneTimeSafetyChecker implements DevicePolicySafetyChecker { + ", should be " + operationToString(mOperation)); } String reasonName = operationSafetyReasonToString(reason); - DevicePolicyManagerInternal dpmi = LocalServices - .getService(DevicePolicyManagerInternal.class); + DevicePolicyManagerLiteInternal dpmi = LocalServices + .getService(DevicePolicyManagerLiteInternal.class); Slog.i(TAG, "notifying " + reasonName + " is UNSAFE"); dpmi.notifyUnsafeOperationStateChanged(this, reason, /* isSafe= */ false); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java index c29de905d370..0741c81bb528 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java @@ -64,7 +64,7 @@ class SecurityLogMonitor implements Runnable { mLastForceNanos = System.nanoTime(); } - private static final boolean DEBUG = false; // STOPSHIP if true. + private static final boolean DEBUG = true; // STOPSHIP if true. private static final String TAG = "SecurityLogMonitor"; /** * Each log entry can hold up to 4K bytes (but as of {@link android.os.Build.VERSION_CODES#N} @@ -427,7 +427,7 @@ class SecurityLogMonitor implements Runnable { while (!Thread.currentThread().isInterrupted()) { try { final boolean force = mForceSemaphore.tryAcquire(POLLING_INTERVAL_MS, MILLISECONDS); - + if (DEBUG) Slog.d(TAG, "Retrieving next batch, force=" + force); getNextBatch(newLogs); mLock.lockInterruptibly(); @@ -469,6 +469,11 @@ class SecurityLogMonitor implements Runnable { return; } final int logSize = mPendingLogs.size(); + if (DEBUG) { + Slog.d(TAG, String.format( + "notifyDeviceOwnerOrProfileOwnerIfNeeded, size: %d now: %d next: %d", + logSize, SystemClock.elapsedRealtime(), mNextAllowedRetrievalTimeMillis)); + } if (logSize >= BUFFER_ENTRIES_NOTIFICATION_LEVEL || (force && logSize > 0)) { // Allow DO to retrieve logs if too many pending logs or if forced. if (!mAllowedToRetrieve) { @@ -507,6 +512,7 @@ class SecurityLogMonitor implements Runnable { synchronized (mForceSemaphore) { final long toWaitNanos = mLastForceNanos + FORCE_FETCH_THROTTLE_NS - nowNanos; if (toWaitNanos > 0) { + if (DEBUG) Slog.d(TAG, "Forcing security logs throttled"); return NANOSECONDS.toMillis(toWaitNanos) + 1; // Round up. } mLastForceNanos = nowNanos; @@ -515,6 +521,7 @@ class SecurityLogMonitor implements Runnable { if (mForceSemaphore.availablePermits() == 0) { mForceSemaphore.release(); } + if (DEBUG) Slog.d(TAG, "Forcing security logs semaphore released"); return 0; } } 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 21de7916e23d..280204dfd481 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -2309,12 +2309,14 @@ public class AlarmManagerServiceTest { public void minWindowChangeDisabled() { mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, false); final long minWindow = 73; + final long futurity = 10_000; setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. for (int window = 1; window <= minWindow; window++) { final PendingIntent pi = getNewMockPendingIntent(); - setTestAlarm(ELAPSED_REALTIME, 0, window, pi, 0, 0, TEST_CALLING_UID, null); + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, pi, 0, 0, + TEST_CALLING_UID, null); assertEquals(1, mService.mAlarmStore.size()); final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); @@ -2323,18 +2325,61 @@ public class AlarmManagerServiceTest { } @Test + public void minWindowExempted() { + mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, true); + final long minWindow = 73; + final long futurity = 10_000; + + setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); + + final int coreUid = 2312; + doReturn(true).when(() -> UserHandle.isCore(coreUid)); + + final int allowlisted = 54239; + when(mDeviceIdleInternal.isAppOnWhitelist(UserHandle.getAppId(allowlisted))).thenReturn( + true); + + for (final int callingUid : new int[]{SYSTEM_UI_UID, coreUid, coreUid}) { + // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. + for (int window = 1; window <= minWindow; window++) { + final PendingIntent pi = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, pi, 0, 0, + callingUid, null); + + assertEquals(1, mService.mAlarmStore.size()); + final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); + assertEquals(window, a.windowLength); + } + } + + // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. + for (int window = 1; window <= minWindow; window++) { + final PendingIntent pi = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, pi, 0, 0, + TEST_CALLING_UID, null); + + assertEquals(1, mService.mAlarmStore.size()); + final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); + assertEquals(minWindow, a.windowLength); + } + } + + @Test public void minWindowPriorityAlarm() { mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, true); final long minWindow = 73; + final long futurity = 10_000; setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. for (int window = 1; window <= minWindow; window++) { - setPrioritizedAlarm(ELAPSED_REALTIME, 0, window, new IAlarmListener.Stub() { - @Override - public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { - } - }); + setPrioritizedAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, + new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) + throws RemoteException { + } + }); assertEquals(1, mService.mAlarmStore.size()); final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); assertEquals(window, a.windowLength); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java index cf5db2e0db98..8532dbb7f38a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java @@ -158,7 +158,7 @@ public class MockableLocationProviderTest { @Test public void testSetState() { - assertThat(mProvider.isAllowed()).isFalse(); + assertThat(mProvider.getState().allowed).isFalse(); AbstractLocationProvider.State newState; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java index fa3f45c08202..a2b1c1cb1a49 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java @@ -34,6 +34,7 @@ import static org.mockito.Mockito.when; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; +import android.app.admin.DevicePolicyManagerLiteInternal; import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; @@ -158,6 +159,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { final long ident = mContext.binder.clearCallingIdentity(); try { + LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext); @@ -271,6 +273,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { final long ident = mContext.binder.clearCallingIdentity(); try { + LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext); @@ -339,6 +342,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { // (Need clearCallingIdentity() to pass permission checks.) final long ident = mContext.binder.clearCallingIdentity(); try { + LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext); @@ -499,6 +503,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { DevicePolicyManagerServiceTestable dpms; final long ident = mContext.binder.clearCallingIdentity(); try { + LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 5447a58a1643..cedf6361e33b 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -84,6 +84,7 @@ import android.app.PendingIntent; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; +import android.app.admin.DevicePolicyManagerLiteInternal; import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.PasswordMetrics; import android.app.admin.SystemUpdatePolicy; @@ -280,6 +281,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { private void initializeDpms() { // Need clearCallingIdentity() to pass permission checks. final long ident = mContext.binder.clearCallingIdentity(); + LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext); @@ -369,10 +371,14 @@ public class DevicePolicyManagerTest extends DpmTestBase { .thenReturn(false); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class); new DevicePolicyManagerServiceTestable(getServices(), mContext); // If the device has no DPMS feature, it shouldn't register the local service. assertThat(LocalServices.getService(DevicePolicyManagerInternal.class)).isNull(); + + // But should still register the lite one + assertThat(LocalServices.getService(DevicePolicyManagerLiteInternal.class)).isNotNull(); } @Test diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 7241fa00ecf7..90a127701505 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -253,6 +253,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setPerson(makePerson("person", "personKey", "personUri")) .setLongLived(true) .setExtras(pb) + .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen) .build(); si.addFlags(ShortcutInfo.FLAG_PINNED); si.setBitmapPath("abc"); @@ -288,6 +289,8 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(null, si.getTextResName()); assertEquals(0, si.getDisabledMessageResourceId()); assertEquals(null, si.getDisabledMessageResName()); + assertEquals("android:style/Theme.Black.NoTitleBar.Fullscreen", + si.getStartingThemeResName()); } public void testShortcutInfoParcel_resId() { @@ -308,6 +311,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz")) .setRank(123) .setExtras(pb) + .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen) .build(); si.addFlags(ShortcutInfo.FLAG_PINNED); si.setBitmapPath("abc"); @@ -339,6 +343,8 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(456, si.getIconResourceId()); assertEquals("string/r456", si.getIconResName()); assertEquals("test_uri", si.getIconUri()); + assertEquals("android:style/Theme.Black.NoTitleBar.Fullscreen", + si.getStartingThemeResName()); } public void testShortcutInfoClone() { @@ -2210,6 +2216,10 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { android.R.drawable.alert_dark_frame, true, getTestContext().getPackageName())); assertEquals("" + android.R.string.cancel, ShortcutInfo.lookUpResourceName(res, android.R.string.cancel, false, getTestContext().getPackageName())); + assertEquals("" + android.R.style.Theme_Black_NoTitleBar_Fullscreen, + ShortcutInfo.lookUpResourceName( + res, android.R.style.Theme_Black_NoTitleBar_Fullscreen, true, + getTestContext().getPackageName())); } public void testLookUpResourceName_appResources() { @@ -2236,6 +2246,10 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(android.R.drawable.alert_dark_frame, ShortcutInfo.lookUpResourceId(res, "" + android.R.drawable.alert_dark_frame, null, getTestContext().getPackageName())); + assertEquals(android.R.style.Theme_Black_NoTitleBar_Fullscreen, + ShortcutInfo.lookUpResourceId( + res, "" + android.R.style.Theme_Black_NoTitleBar_Fullscreen, + null, getTestContext().getPackageName())); } // Test for a ShortcutInfo method. diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index 5614aa2a165d..577e36c7d5db 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -394,7 +394,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { "disabledMessage", 0, "disabledMessageResName", null, null, 0, null, 0, 0, 0, "iconResName", "bitmapPath", null, 0, - null, null, 0); + null, null, null); return si; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 70ba2cf17880..a522b5c6161e 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -6472,7 +6472,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { zenPolicy, NotificationManager.INTERRUPTION_FILTER_NONE, isEnabled); try { - mBinderService.addAutomaticZenRule(rule); + mBinderService.addAutomaticZenRule(rule, mContext.getPackageName()); fail("Zen policy only applies to priority only mode"); } catch (IllegalArgumentException e) { // yay @@ -6480,11 +6480,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class), zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled); - mBinderService.addAutomaticZenRule(rule); + mBinderService.addAutomaticZenRule(rule, mContext.getPackageName()); rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class), null, NotificationManager.INTERRUPTION_FILTER_NONE, isEnabled); - mBinderService.addAutomaticZenRule(rule); + mBinderService.addAutomaticZenRule(rule, mContext.getPackageName()); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index 5262465a399c..d0bf63a1680f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -190,7 +190,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.configurationActivity = new ComponentName("a", "a"); - rule.component = new ComponentName("a", "b"); + rule.component = new ComponentName("b", "b"); rule.conditionId = new Uri.Builder().scheme("hello").build(); rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE); rule.enabled = true; @@ -200,6 +200,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.modified = true; rule.name = "name"; rule.snoozing = true; + rule.pkg = "b"; TypedXmlSerializer out = Xml.newFastSerializer(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -215,8 +216,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { new ByteArrayInputStream(baos.toByteArray())), null); parser.nextTag(); ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser); - // read from backing component - assertEquals("a", fromXml.pkg); + assertEquals("b", fromXml.pkg); // always resets on reboot assertFalse(fromXml.snoozing); //should all match original @@ -232,6 +232,55 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.zenMode, fromXml.zenMode); } + @Test + public void testRuleXml_pkg_component() throws Exception { + String tag = "tag"; + + ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); + rule.configurationActivity = new ComponentName("a", "a"); + rule.component = new ComponentName("b", "b"); + + TypedXmlSerializer out = Xml.newFastSerializer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + out.setOutput(new BufferedOutputStream(baos), "utf-8"); + out.startDocument(null, true); + out.startTag(null, tag); + ZenModeConfig.writeRuleXml(rule, out); + out.endTag(null, tag); + out.endDocument(); + + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), null); + parser.nextTag(); + ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser); + assertEquals("b", fromXml.pkg); + } + + @Test + public void testRuleXml_pkg_configActivity() throws Exception { + String tag = "tag"; + + ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); + rule.configurationActivity = new ComponentName("a", "a"); + + TypedXmlSerializer out = Xml.newFastSerializer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + out.setOutput(new BufferedOutputStream(baos), "utf-8"); + out.startDocument(null, true); + out.startTag(null, tag); + ZenModeConfig.writeRuleXml(rule, out); + out.endTag(null, tag); + out.endDocument(); + + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), null); + parser.nextTag(); + ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser); + assertNull(fromXml.pkg); + } + private ZenModeConfig getMutedRingerConfig() { ZenModeConfig config = new ZenModeConfig(); // Allow alarms, media diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 72c6028e5ad3..00dbaf649ca2 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -1597,7 +1597,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ComponentName("android", "ScheduleConditionProvider"), ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelperSpy.addAutomaticZenRule(zenRule, "test"); + String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test"); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelperSpy.mConfig.automaticRules.get(id); @@ -1617,12 +1617,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ComponentName("android", "ScheduleConditionProvider"), sharedUri, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelperSpy.addAutomaticZenRule(zenRule, "test"); + String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test"); AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", new ComponentName("android", "ScheduleConditionProvider"), sharedUri, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id2 = mZenModeHelperSpy.addAutomaticZenRule(zenRule2, "test"); + String id2 = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule2, "test"); Condition condition = new Condition(sharedUri, "", Condition.STATE_TRUE); mZenModeHelperSpy.setAutomaticZenRuleState(sharedUri, condition); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index d2270b55b954..4e261deb67f6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -1534,30 +1534,6 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testSandboxDisplayApis_unresizableAppNotSandboxed() { - // Set up a display in landscape with an unresizable app. - setUpDisplaySizeWithApp(2500, 1000); - mActivity.mDisplayContent.setSandboxDisplayApis(false /* sandboxDisplayApis */); - prepareUnresizable(mActivity, 1.5f, SCREEN_ORIENTATION_LANDSCAPE); - assertFitted(); - - // Activity max bounds not be sandboxed since sandboxing is disabled. - assertMaxBoundsInheritDisplayAreaBounds(); - } - - @Test - public void testSandboxDisplayApis_unresizableAppSandboxed() { - // Set up a display in landscape with an unresizable app. - setUpDisplaySizeWithApp(2500, 1000); - mActivity.mDisplayContent.setSandboxDisplayApis(true /* sandboxDisplayApis */); - prepareUnresizable(mActivity, 1.5f, SCREEN_ORIENTATION_LANDSCAPE); - assertFitted(); - - // Activity max bounds should be sandboxed since sandboxing is enabled. - assertActivityMaxBoundsSandboxed(); - } - - @Test public void testResizableApp_notSandboxed() { // Set up a display in landscape with a fully resizable app. setUpDisplaySizeWithApp(2500, 1000); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 3f1248a5fff7..a1b3159825fb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -796,6 +796,9 @@ public class WindowOrganizerTests extends WindowTestsBase { @Override public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { } + @Override + public void onAppSplashScreenViewRemoved(int taskId) { + } }; private ActivityRecord makePipableActivity() { |