diff options
256 files changed, 4565 insertions, 7356 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index a60ced5835ea..f249884cb1a0 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -107,7 +107,7 @@ aconfig_declarations_group { "com.android.server.flags.services-aconfig-java", "com.android.text.flags-aconfig-java", "com.android.window.flags.window-aconfig-java", - "configinfra_framework_flags_java_lib", + "configinfra_framework_flags_java_exported_lib", "conscrypt_exported_aconfig_flags_lib", "device_policy_aconfig_flags_lib", "display_flags_lib", @@ -467,7 +467,7 @@ java_aconfig_library { "//apex_available:platform", "com.android.art", "com.android.art.debug", - "com.android.btservices", + "com.android.bt", "com.android.mediaprovider", "com.android.permission", ], 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 60ba3b896a28..829442aed6ac 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -96,6 +96,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.UserPackage; +import android.content.res.Resources; import android.net.Uri; import android.os.BatteryManager; import android.os.BatteryStatsInternal; @@ -1784,7 +1785,8 @@ public class AlarmManagerService extends SystemService { mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms() - && UserManager.supportsMultipleUsers(); + && UserManager.supportsMultipleUsers() && Resources.getSystem().getBoolean( + com.android.internal.R.bool.config_allowAlarmsOnStoppedUsers); if (mStartUserBeforeScheduledAlarms) { mUserWakeupStore = new UserWakeupStore(); mUserWakeupStore.init(); diff --git a/api/api.go b/api/api.go index cbdb7e81ab86..640773be0f9b 100644 --- a/api/api.go +++ b/api/api.go @@ -104,8 +104,9 @@ func (a *CombinedApis) DepsMutator(ctx android.BottomUpMutatorContext) { } func (a *CombinedApis) GenerateAndroidBuildActions(ctx android.ModuleContext) { - ctx.WalkDeps(func(child, parent android.Module) bool { - if _, ok := android.OtherModuleProvider(ctx, child, java.AndroidLibraryInfoProvider); ok && child.Name() != "framework-res" { + ctx.WalkDepsProxy(func(child, parent android.ModuleProxy) bool { + javaInfo, ok := android.OtherModuleProvider(ctx, child, java.JavaInfoProvider) + if ok && javaInfo.AndroidLibraryDependencyInfo != nil && child.Name() != "framework-res" { // Stubs of BCP and SSCP libraries should not have any dependencies on apps // This check ensures that we do not run into circular dependencies when UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT=true ctx.ModuleErrorf( diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java index f34341fd14e1..3214bd8f01fc 100644 --- a/core/java/android/app/ApplicationStartInfo.java +++ b/core/java/android/app/ApplicationStartInfo.java @@ -729,6 +729,7 @@ public final class ApplicationStartInfo implements Parcelable { return 0; } + // LINT.IfChange(write_parcel) @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mStartupState); @@ -753,6 +754,7 @@ public final class ApplicationStartInfo implements Parcelable { dest.writeLong(mMonoticCreationTimeMs); dest.writeInt(mStartComponent); } + // LINT.ThenChange(:read_parcel) /** @hide */ public ApplicationStartInfo(long monotonicCreationTimeMs) { @@ -779,6 +781,7 @@ public final class ApplicationStartInfo implements Parcelable { } /** @hide */ + // LINT.IfChange(read_parcel) @VisibleForTesting public ApplicationStartInfo(@NonNull Parcel in) { mStartupState = in.readInt(); @@ -803,6 +806,7 @@ public final class ApplicationStartInfo implements Parcelable { mMonoticCreationTimeMs = in.readLong(); mStartComponent = in.readInt(); } + // LINT.ThenChange(:write_parcel) private static String intern(@Nullable String source) { return source != null ? source.intern() : null; @@ -835,6 +839,7 @@ public final class ApplicationStartInfo implements Parcelable { * @param fieldId Field Id of the ApplicationStartInfo as defined in the parent message * @hide */ + // LINT.IfChange(write_proto) public void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException { final long token = proto.start(fieldId); proto.write(ApplicationStartInfoProto.PID, mPid); @@ -884,6 +889,7 @@ public final class ApplicationStartInfo implements Parcelable { proto.write(ApplicationStartInfoProto.START_COMPONENT, mStartComponent); proto.end(token); } + // LINT.ThenChange(:read_proto) /** * Read from a protocol buffer input stream. Protocol buffer message definition at {@link @@ -893,6 +899,7 @@ public final class ApplicationStartInfo implements Parcelable { * @param fieldId Field Id of the ApplicationStartInfo as defined in the parent message * @hide */ + // LINT.IfChange(read_proto) public void readFromProto(ProtoInputStream proto, long fieldId) throws IOException, WireTypeMismatchException, ClassNotFoundException { final long token = proto.start(fieldId); @@ -976,6 +983,7 @@ public final class ApplicationStartInfo implements Parcelable { } proto.end(token); } + // LINT.ThenChange(:write_proto) /** @hide */ public void dump(@NonNull PrintWriter pw, @Nullable String prefix, @Nullable String seqSuffix, diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index d22926791cc3..24f2495d8f09 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -68,6 +68,7 @@ import android.service.notification.StatusBarNotification; import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenModeConfig; import android.service.notification.ZenPolicy; +import android.text.TextUtils; import android.util.Log; import android.util.LruCache; import android.util.Slog; @@ -77,6 +78,8 @@ import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.time.Duration; +import java.time.Instant; import java.time.InstantSource; import java.util.ArrayList; import java.util.Arrays; @@ -661,8 +664,10 @@ public class NotificationManager { mCallNotificationEventCallbacks = new HashMap<>(); private final InstantSource mClock; - private final RateEstimator mUpdateRateEstimator = new RateEstimator(); - private final RateEstimator mUnnecessaryCancelRateEstimator = new RateEstimator(); + private final RateLimiter mUpdateRateLimiter = new RateLimiter("notify (update)", + MAX_NOTIFICATION_UPDATE_RATE); + private final RateLimiter mUnnecessaryCancelRateLimiter = new RateLimiter("cancel (dupe)", + MAX_NOTIFICATION_UNNECESSARY_CANCEL_RATE); // Value is KNOWN_STATUS_ENQUEUED/_CANCELLED private final LruCache<NotificationKey, Integer> mKnownNotifications = new LruCache<>(100); private final Object mThrottleLock = new Object(); @@ -820,21 +825,16 @@ public class NotificationManager { if (Flags.nmBinderPerfThrottleNotify()) { NotificationKey key = new NotificationKey(user, pkg, tag, id); - long now = mClock.millis(); synchronized (mThrottleLock) { Integer status = mKnownNotifications.get(key); if (status != null && status == KNOWN_STATUS_ENQUEUED && !notification.hasCompletedProgress()) { - float updateRate = mUpdateRateEstimator.getRate(now); - if (updateRate > MAX_NOTIFICATION_UPDATE_RATE) { - Slog.w(TAG, "Shedding update of " + key - + ", notification update maximum rate exceeded (" + updateRate - + ")"); + if (mUpdateRateLimiter.eventExceedsRate()) { + mUpdateRateLimiter.recordRejected(key); return true; } - mUpdateRateEstimator.update(now); + mUpdateRateLimiter.recordAccepted(); } - mKnownNotifications.put(key, KNOWN_STATUS_ENQUEUED); } } @@ -845,6 +845,51 @@ public class NotificationManager { private record NotificationKey(@NonNull UserHandle user, @NonNull String pkg, @Nullable String tag, int id) { } + /** Helper class to rate-limit Binder calls. */ + private class RateLimiter { + + private static final Duration RATE_LIMITER_LOG_INTERVAL = Duration.ofSeconds(5); + + private final RateEstimator mInputRateEstimator; + private final RateEstimator mOutputRateEstimator; + private final String mName; + private final float mLimitRate; + + private Instant mLogSilencedUntil; + + private RateLimiter(String name, float limitRate) { + mInputRateEstimator = new RateEstimator(); + mOutputRateEstimator = new RateEstimator(); + mName = name; + mLimitRate = limitRate; + } + + boolean eventExceedsRate() { + long nowMillis = mClock.millis(); + mInputRateEstimator.update(nowMillis); + return mOutputRateEstimator.getRate(nowMillis) > mLimitRate; + } + + void recordAccepted() { + mOutputRateEstimator.update(mClock.millis()); + } + + void recordRejected(NotificationKey key) { + Instant now = mClock.instant(); + if (mLogSilencedUntil != null && now.isBefore(mLogSilencedUntil)) { + return; + } + + long nowMillis = now.toEpochMilli(); + Slog.w(TAG, TextUtils.formatSimple( + "Shedding %s of %s, rate limit (%s) exceeded: input %s, output would be %s", + mName, key, mLimitRate, mInputRateEstimator.getRate(nowMillis), + mOutputRateEstimator.getRate(nowMillis))); + + mLogSilencedUntil = now.plus(RATE_LIMITER_LOG_INTERVAL); + } + } + private Notification fixNotification(Notification notification) { String pkg = mContext.getPackageName(); // Fix the notification as best we can. @@ -967,18 +1012,14 @@ public class NotificationManager { private boolean discardCancel(UserHandle user, String pkg, @Nullable String tag, int id) { if (Flags.nmBinderPerfThrottleNotify()) { NotificationKey key = new NotificationKey(user, pkg, tag, id); - long now = mClock.millis(); synchronized (mThrottleLock) { Integer status = mKnownNotifications.get(key); if (status != null && status == KNOWN_STATUS_CANCELLED) { - float cancelRate = mUnnecessaryCancelRateEstimator.getRate(now); - if (cancelRate > MAX_NOTIFICATION_UNNECESSARY_CANCEL_RATE) { - Slog.w(TAG, "Shedding cancel of " + key - + ", presumably unnecessary and maximum rate exceeded (" - + cancelRate + ")"); + if (mUnnecessaryCancelRateLimiter.eventExceedsRate()) { + mUnnecessaryCancelRateLimiter.recordRejected(key); return true; } - mUnnecessaryCancelRateEstimator.update(now); + mUnnecessaryCancelRateLimiter.recordAccepted(); } mKnownNotifications.put(key, KNOWN_STATUS_CANCELLED); } diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index b1db1379e400..edd17e8bb552 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -13,6 +13,13 @@ flag { } flag { + name: "notifications_redesign_themed_app_icons" + namespace: "systemui" + description: "Notifications Redesign: Experiment to make app icons in notifications themed" + bug: "371174789" +} + +flag { name: "notifications_redesign_templates" namespace: "systemui" description: "Notifications Redesign: Update notification templates" @@ -174,16 +181,6 @@ flag { } flag { - name: "update_ranking_time" - namespace: "systemui" - description: "Updates notification sorting criteria to highlight new content while maintaining stability" - bug: "326016985" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "sort_section_by_time" namespace: "systemui" description: "Changes notification sort order to be by time within a section" diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl b/core/java/android/app/supervision/ISupervisionAppService.aidl index 013158676f79..033998fc4a5b 100644 --- a/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl +++ b/core/java/android/app/supervision/ISupervisionAppService.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,10 @@ * limitations under the License. */ -package android.service.watchdog; +package android.app.supervision; /** * @hide */ -parcelable PackageConfig; +interface ISupervisionAppService { +} diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl b/core/java/android/app/supervision/SupervisionAppService.java index 90965092ac2b..4468c78cbd34 100644 --- a/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl +++ b/core/java/android/app/supervision/SupervisionAppService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,24 @@ * limitations under the License. */ -package android.service.watchdog; +package android.app.supervision; -import android.os.RemoteCallback; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; /** + * Base class for a service that the {@code android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION} + * role holder must implement. + * * @hide */ -@PermissionManuallyEnforced -oneway interface IExplicitHealthCheckService -{ - void setCallback(in @nullable RemoteCallback callback); - void request(String packageName); - void cancel(String packageName); - void getSupportedPackages(in RemoteCallback callback); - void getRequestedPackages(in RemoteCallback callback); +public class SupervisionAppService extends Service { + private final ISupervisionAppService mBinder = new ISupervisionAppService.Stub() { + }; + + @Override + public final IBinder onBind(Intent intent) { + return mBinder.asBinder(); + } } diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig index 1b0353274fb9..4ee3a0360b43 100644 --- a/core/java/android/app/supervision/flags.aconfig +++ b/core/java/android/app/supervision/flags.aconfig @@ -32,3 +32,11 @@ flag { description: "Flag that deprecates supervision methods in DPM" bug: "382034839" } + +flag { + name: "enable_supervision_app_service" + is_exported: true + namespace: "supervision" + description: "Flag to enable the SupervisionAppService" + bug: "389123070" +} diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index 48fa0c8277df..effe5554aff4 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -112,32 +112,6 @@ { "file_patterns": ["Bugreport[^/]*\\.java"], "name": "ShellTests" - }, - { - "file_patterns": [ - "CpuHeadroom[^/]*", - "GpuHeadroom[^/]*", - "health/SystemHealthManager\\.java" - ], - "name": "CtsOsTestCases", - "options": [ - {"include-filter": "android.os.health.cts.HeadroomTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] - }, - { - "file_patterns": [ - "CpuHeadroom[^/]*", - "GpuHeadroom[^/]*", - "health/SystemHealthManager\\.java" - ], - "name": "FrameworksCoreTests", - "options": [ - {"include-filter": "android.os.SystemHealthManagerUnitTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] } ], "ravenwood-presubmit": [ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 73d1e1701eec..0cfec2cc7314 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -12869,19 +12869,6 @@ public final class Settings { */ public static final String DISABLE_SECURE_WINDOWS = "disable_secure_windows"; - /** - * Controls if the adaptive authentication feature should be disabled, which - * will attempt to lock the device after a number of consecutive authentication - * attempts fail. - * - * This can only be disabled on debuggable builds. Set to 1 to disable or 0 for the - * normal behavior. - * - * @hide - */ - public static final String DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK = - "disable_adaptive_auth_limit_lock"; - /** @hide */ public static final int PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK = 0; /** @hide */ diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index a5586227cbb3..4a9e945e62a9 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -155,11 +155,4 @@ flag { description: "Feature flag to add the privileged flag to the SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE permission" bug: "380120712" is_fixed_read_only: true -} - -flag { - name: "disable_adaptive_auth_counter_lock" - namespace: "biometrics" - description: "Flag to allow an adb secure setting to disable the adaptive auth lock" - bug: "371057865" -} +}
\ No newline at end of file diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java index eea93b321e47..2e661b475dc9 100644 --- a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java +++ b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java @@ -34,8 +34,7 @@ import java.lang.annotation.RetentionPolicy; * This objects represents a value that can be used for a particular settings preference. * <p>The data type for the value will correspond to {@link #getType}. For possible types, see * constants below, such as {@link #TYPE_BOOLEAN} and {@link #TYPE_STRING}. - * Depending on the type, the corresponding getter will contain its value. All other getters will - * return default values (boolean returns false, String returns null) so they should not be used. + * Depending on the type, the corresponding getter will contain its value. * <p>See documentation on the constants for which getter method should be used. */ @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) @@ -43,12 +42,7 @@ public final class SettingsPreferenceValue implements Parcelable { @Type private final int mType; - private final boolean mBooleanValue; - private final int mIntValue; - private final long mLongValue; - private final double mDoubleValue; - @Nullable - private final String mStringValue; + private final @Nullable Object mValue; /** * Returns the type indicator for Preference value. @@ -59,39 +53,39 @@ public final class SettingsPreferenceValue implements Parcelable { } /** - * Returns the boolean value for Preference if type is {@link #TYPE_BOOLEAN}. + * Returns the boolean value for Preference, the type must be {@link #TYPE_BOOLEAN}. */ public boolean getBooleanValue() { - return mBooleanValue; + return (boolean) mValue; } /** - * Returns the int value for Preference if type is {@link #TYPE_INT}. + * Returns the int value for Preference, the type must be {@link #TYPE_INT}. */ public int getIntValue() { - return mIntValue; + return (int) mValue; } /** - * Returns the long value for Preference if type is {@link #TYPE_LONG}. + * Returns the long value for Preference, the type must be {@link #TYPE_LONG}. */ public long getLongValue() { - return mLongValue; + return (long) mValue; } /** - * Returns the double value for Preference if type is {@link #TYPE_DOUBLE}. + * Returns the double value for Preference, the type must be {@link #TYPE_DOUBLE}. */ public double getDoubleValue() { - return mDoubleValue; + return (double) mValue; } /** - * Returns the string value for Preference if type is {@link #TYPE_STRING}. + * Returns the string value for Preference, the type must be {@link #TYPE_STRING}. */ @Nullable public String getStringValue() { - return mStringValue; + return (String) mValue; } /** @hide */ @@ -115,34 +109,47 @@ public final class SettingsPreferenceValue implements Parcelable { public static final int TYPE_STRING = 3; /** Value is of type int. Access via {@link #getIntValue}. */ public static final int TYPE_INT = 4; + /** Max type value. */ + private static final int MAX_TYPE_VALUE = TYPE_INT; private SettingsPreferenceValue(@NonNull Builder builder) { mType = builder.mType; - mBooleanValue = builder.mBooleanValue; - mLongValue = builder.mLongValue; - mDoubleValue = builder.mDoubleValue; - mStringValue = builder.mStringValue; - mIntValue = builder.mIntValue; + mValue = builder.mValue; } private SettingsPreferenceValue(@NonNull Parcel in) { mType = in.readInt(); - mBooleanValue = in.readBoolean(); - mLongValue = in.readLong(); - mDoubleValue = in.readDouble(); - mStringValue = in.readString8(); - mIntValue = in.readInt(); + if (mType == TYPE_BOOLEAN) { + mValue = in.readBoolean(); + } else if (mType == TYPE_LONG) { + mValue = in.readLong(); + } else if (mType == TYPE_DOUBLE) { + mValue = in.readDouble(); + } else if (mType == TYPE_STRING) { + mValue = in.readString(); + } else if (mType == TYPE_INT) { + mValue = in.readInt(); + } else { + // throw exception immediately, further read to Parcel may be invalid + throw new IllegalStateException("Unknown type: " + mType); + } } /** @hide */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mType); - dest.writeBoolean(mBooleanValue); - dest.writeLong(mLongValue); - dest.writeDouble(mDoubleValue); - dest.writeString8(mStringValue); - dest.writeInt(mIntValue); + if (mType == TYPE_BOOLEAN) { + dest.writeBoolean(getBooleanValue()); + } else if (mType == TYPE_LONG) { + dest.writeLong(getLongValue()); + } else if (mType == TYPE_DOUBLE) { + dest.writeDouble(getDoubleValue()); + } else if (mType == TYPE_STRING) { + dest.writeString(getStringValue()); + } else if (mType == TYPE_INT) { + dest.writeInt(getIntValue()); + } } /** @hide */ @@ -174,17 +181,16 @@ public final class SettingsPreferenceValue implements Parcelable { public static final class Builder { @Type private final int mType; - private boolean mBooleanValue; - private long mLongValue; - private double mDoubleValue; - private String mStringValue; - private int mIntValue; + private @Nullable Object mValue; /** * Create Builder instance. * @param type type indicator for preference value */ public Builder(@Type int type) { + if (type < 0 || type > MAX_TYPE_VALUE) { + throw new IllegalArgumentException("Unknown type: " + type); + } mType = type; } @@ -194,7 +200,8 @@ public final class SettingsPreferenceValue implements Parcelable { @SuppressLint("MissingGetterMatchingBuilder") @NonNull public Builder setBooleanValue(boolean booleanValue) { - mBooleanValue = booleanValue; + checkType(TYPE_BOOLEAN); + mValue = booleanValue; return this; } @@ -203,7 +210,8 @@ public final class SettingsPreferenceValue implements Parcelable { */ @NonNull public Builder setIntValue(int intValue) { - mIntValue = intValue; + checkType(TYPE_INT); + mValue = intValue; return this; } @@ -212,7 +220,8 @@ public final class SettingsPreferenceValue implements Parcelable { */ @NonNull public Builder setLongValue(long longValue) { - mLongValue = longValue; + checkType(TYPE_LONG); + mValue = longValue; return this; } @@ -221,7 +230,8 @@ public final class SettingsPreferenceValue implements Parcelable { */ @NonNull public Builder setDoubleValue(double doubleValue) { - mDoubleValue = doubleValue; + checkType(TYPE_DOUBLE); + mValue = doubleValue; return this; } @@ -230,10 +240,17 @@ public final class SettingsPreferenceValue implements Parcelable { */ @NonNull public Builder setStringValue(@Nullable String stringValue) { - mStringValue = stringValue; + checkType(TYPE_STRING); + mValue = stringValue; return this; } + private void checkType(int type) { + if (mType != type) { + throw new IllegalArgumentException("Type is: " + mType); + } + } + /** * Constructs an immutable {@link SettingsPreferenceValue} object. */ diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 3c53506990d1..323d83b92143 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1066,6 +1066,12 @@ public abstract class Layout { var hasBgColorChanged = newBackground != bgPaint.getColor(); if (lineNum != mLastLineNum || hasBgColorChanged) { + // Skip processing if the character is a space or a tap to avoid + // rendering an abrupt, empty rectangle. + if (Character.isWhitespace(mText.charAt(index))) { + return; + } + // Draw what we have so far, then reset the rect and update its color drawRect(); mLineBackground.set(left, top, right, bottom); diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java index 6f7660a0fb3d..9d0ea547e734 100644 --- a/core/java/android/window/TaskFragmentOperation.java +++ b/core/java/android/window/TaskFragmentOperation.java @@ -161,6 +161,16 @@ public final class TaskFragmentOperation implements Parcelable { */ public static final int OP_TYPE_SET_PINNED = 19; + /** + * Sets whether this TaskFragment can affect system UI flags such as the status bar. Default + * is {@code true}. + * + * This operation is only allowed for system organizers. See + * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer( + * ITaskFragmentOrganizer, boolean)} + */ + public static final int OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS = 20; + @IntDef(prefix = { "OP_TYPE_" }, value = { OP_TYPE_UNKNOWN, OP_TYPE_CREATE_TASK_FRAGMENT, @@ -183,6 +193,7 @@ public final class TaskFragmentOperation implements Parcelable { OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH, OP_TYPE_SET_DECOR_SURFACE_BOOSTED, OP_TYPE_SET_PINNED, + OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS, }) @Retention(RetentionPolicy.SOURCE) public @interface OperationType {} diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 7a1078f8718f..8b1fd6c50c99 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -432,3 +432,19 @@ flag { bug: "384976265" } +flag { + name: "aod_transition" + namespace: "windowing_frontend" + description: "Support to show lock wallpaper in aod state" + bug: "361438779" +} + +flag { + name: "check_disabled_snapshots_in_task_persister" + namespace: "windowing_frontend" + description: "Check for TaskSnapshots disabling in TaskSnapshotPersister." + bug: "387915176" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java index d1adfc95461d..158b526cb1a0 100644 --- a/core/java/com/android/internal/jank/Cuj.java +++ b/core/java/com/android/internal/jank/Cuj.java @@ -257,8 +257,16 @@ public class Cuj { */ public static final int CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU = 120; + /** Track Launcher Overview Task Dismiss animation. + * + * <p>Tracking starts when the overview task is dismissed via + * {@link com.android.quickstep.views.RecentsView#dismissTask}. Tracking finishes when the + * animation to dismiss the overview task ends. + */ + public static final int CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS = 121; + // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE. - @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU; + @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS; /** @hide */ @IntDef({ @@ -370,7 +378,8 @@ public class Cuj { CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE, CUJ_DESKTOP_MODE_SNAP_RESIZE, CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW, - CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU + CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU, + CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS }) @Retention(RetentionPolicy.SOURCE) public @interface CujType {} @@ -493,6 +502,7 @@ public class Cuj { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_SNAP_RESIZE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_SNAP_RESIZE; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_UNMAXIMIZE_WINDOW; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OVERVIEW_TASK_DISMISS; } private Cuj() { @@ -729,6 +739,8 @@ public class Cuj { return "DESKTOP_MODE_UNMAXIMIZE_WINDOW"; case CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU: return "DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU"; + case CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS: + return "LAUNCHER_OVERVIEW_TASK_DISMISS"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 1709ca78af4b..f6de3459a1f5 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -174,7 +174,9 @@ public class RuntimeInit { // System process is dead; ignore } else { try { - Clog_e(TAG, "Error reporting crash", t2); + // Log original crash and then log the error reporting exception. + Clog_e(TAG, "Couldn't report crash. Here's the crash:", e); + Clog_e(TAG, "Error reporting crash. Here's the error:", t2); } catch (Throwable t3) { // Even Clog_e() fails! Oh well. } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ed021b64f7a0..445080215017 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8804,22 +8804,6 @@ <permission android:name="android.permission.RESERVED_FOR_TESTING_SIGNATURE" android:protectionLevel="signature"/> - <!-- - This permission allows the system to receive PACKAGE_CHANGED broadcasts when the component - state of a non-exported component has been changed. - <p>Not for use by third-party applications. </p> - <p>Protection level: internal - @hide - --> - <permission - android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED" - android:protectionLevel="internal" - android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/> - - <uses-permission - android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED" - android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/> - <!-- @SystemApi @FlaggedApi("android.media.tv.flags.kids_mode_tvdb_sharing") This permission is required when accessing information related to @@ -9272,6 +9256,11 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> + <service android:name="com.android.server.ZramMaintenance" + android:exported="false" + android:permission="android.permission.BIND_JOB_SERVICE" > + </service> + <service android:name="com.android.server.ZramWriteback" android:exported="false" android:permission="android.permission.BIND_JOB_SERVICE" > diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index e14cffd72b0c..416e0aeb776c 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3156,6 +3156,16 @@ with admin privileges and admin privileges can be granted/revoked from existing users. --> <bool name="config_enableMultipleAdmins">false</bool> + <!-- Whether to start stopped users before their scheduled alarms. If set to true, users will be + started in background before the alarm time so that it can go off. If false, alarms of + stopped users will not go off and users will remain stopped. --> + <bool name="config_allowAlarmsOnStoppedUsers">true</bool> + + <!-- Whether notification is shown to foreground user when alarm/timer goes off on background + user. If set to true, foreground user will receive a notification with ability to mute + sound or switch user. If false, system notification will not be shown. --> + <bool name="config_showNotificationForBackgroundUserAlarms">true</bool> + <!-- Whether there is a communal profile which should always be running. Only relevant for Headless System User Mode (HSUM) devices. --> <bool name="config_omnipresentCommunalUser">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 68008e57094d..84d51f0b8ad8 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -366,6 +366,8 @@ <java-symbol type="bool" name="config_canSwitchToHeadlessSystemUser"/> <java-symbol type="bool" name="config_enableMultiUserUI"/> <java-symbol type="bool" name="config_enableMultipleAdmins"/> + <java-symbol type="bool" name="config_allowAlarmsOnStoppedUsers"/> + <java-symbol type="bool" name="config_showNotificationForBackgroundUserAlarms"/> <java-symbol type="bool" name="config_bootToHeadlessSystemUser"/> <java-symbol type="bool" name="config_omnipresentCommunalUser"/> <java-symbol type="bool" name="config_enableNewAutoSelectNetworkUI"/> diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java index c7d85d4b9b76..9e78af57b470 100644 --- a/core/tests/coretests/src/android/text/LayoutTest.java +++ b/core/tests/coretests/src/android/text/LayoutTest.java @@ -1024,6 +1024,55 @@ public class LayoutTest { expect.that(backgroundCommands.size()).isEqualTo(backgroundRectsDrawn); } + @Test + @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT) + public void highContrastTextEnabled_testWhitespaceText_DrawsBackgroundsWithAdjacentLetters() { + mTextPaint.setColor(Color.BLACK); + SpannableString spannedText = new SpannableString("Test\tTap and Space"); + + // Set the entire text to white initially + spannedText.setSpan( + new ForegroundColorSpan(Color.WHITE), + /* start= */ 0, + /* end= */ spannedText.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE + ); + + // Find the whitespace character and set its color to black + for (int i = 0; i < spannedText.length(); i++) { + if (Character.isWhitespace(spannedText.charAt(i))) { + spannedText.setSpan( + new ForegroundColorSpan(Color.BLACK), + i, + i + 1, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE + ); + } + } + + Layout layout = new StaticLayout(spannedText, mTextPaint, mWidth, + mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false); + + MockCanvas c = new MockCanvas(/* width= */ 256, /* height= */ 256); + c.setHighContrastTextEnabled(true); + layout.draw( + c, + /* highlightPaths= */ null, + /* highlightPaints= */ null, + /* selectionPath= */ null, + /* selectionPaint= */ null, + /* cursorOffsetVertical= */ 0 + ); + + List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands(); + for (int i = 0; i < drawCommands.size(); i++) { + MockCanvas.DrawCommand drawCommand = drawCommands.get(i); + if (drawCommand.rect != null) { + expect.that(removeAlpha(drawCommand.paint.getColor())).isEqualTo(Color.BLACK); + } + } + } + private int removeAlpha(int color) { return Color.rgb( Color.red(color), diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml index 3403bbfa2384..3b4014867ef7 100644 --- a/data/etc/preinstalled-packages-platform.xml +++ b/data/etc/preinstalled-packages-platform.xml @@ -102,7 +102,7 @@ Changes to the whitelist during system updates can result in installing addition to pre-existing users, but cannot uninstall pre-existing system packages from pre-existing users. --> <config> - <!-- Bluetooth (com.android.btservices apex) - visible on the sharesheet --> + <!-- Bluetooth (com.android.bt apex) - visible on the sharesheet --> <install-in-user-type package="com.android.bluetooth"> <install-in user-type="SYSTEM" /> <install-in user-type="FULL" /> diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index e6c652c14c71..5e93f8db9388 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -20,6 +20,7 @@ import static android.security.keystore2.AndroidKeyStoreCipherSpiBase.DEFAULT_MG import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.content.Context; import android.hardware.security.keymint.EcCurve; import android.hardware.security.keymint.KeyParameter; @@ -732,6 +733,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } } + @RequiresPermission(value = android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + conditional = true) private void addAttestationParameters(@NonNull List<KeyParameter> params) throws ProviderException, IllegalArgumentException, DeviceIdAttestationException { byte[] challenge = mSpec.getAttestationChallenge(); @@ -824,7 +827,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato break; } case AttestationUtils.ID_TYPE_MEID: { - final String meid = telephonyService.getMeid(0); + String meid; + try { + meid = telephonyService.getMeid(0); + } catch (UnsupportedOperationException e) { + Log.e(TAG, "Unable to retrieve MEID", e); + meid = null; + } if (meid == null) { throw new DeviceIdAttestationException("Unable to retrieve MEID"); } diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 957d1b835ec2..bcb6c4f555f7 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -170,9 +170,9 @@ android_library { "res", ], static_libs: [ + "//frameworks/base/packages/SystemUI/aconfig:com_android_systemui_flags_lib", "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", "//frameworks/libs/systemui:iconloader_base", - "//packages/apps/Car/SystemUI/aconfig:com_android_systemui_car_flags_lib", "PlatformAnimationLib", "WindowManager-Shell-lite-proto", "WindowManager-Shell-proto", diff --git a/libs/WindowManager/Shell/aconfig/Android.bp b/libs/WindowManager/Shell/aconfig/Android.bp index 7f8f57b172ff..f8da7fa86cff 100644 --- a/libs/WindowManager/Shell/aconfig/Android.bp +++ b/libs/WindowManager/Shell/aconfig/Android.bp @@ -4,6 +4,7 @@ aconfig_declarations { container: "system", srcs: [ "multitasking.aconfig", + "automotive.aconfig", ], } diff --git a/libs/WindowManager/Shell/aconfig/automotive.aconfig b/libs/WindowManager/Shell/aconfig/automotive.aconfig new file mode 100644 index 000000000000..2f25aa460ec1 --- /dev/null +++ b/libs/WindowManager/Shell/aconfig/automotive.aconfig @@ -0,0 +1,11 @@ +# proto-file: build/make/tools/aconfig/aconfig_protos/protos/aconfig.proto + +package: "com.android.wm.shell" +container: "system" + +flag { + name: "enable_auto_task_stack_controller" + namespace: "multitasking" + description: "Enables auto task stack controller to manage task stacks on automotive" + bug: "384082238" +} diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 73e7223a3aea..b10b099c970b 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -13,13 +13,6 @@ flag { } flag { - name: "enable_split_contextual" - namespace: "multitasking" - description: "Enables invoking split contextually" - bug: "276361926" -} - -flag { name: "enable_taskbar_navbar_unification" namespace: "multitasking" description: "Enables taskbar / navbar unification" diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt index c45f6903c2e1..117ede0d0ac8 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt @@ -290,11 +290,10 @@ class BubbleBarAnimationHelperTest { assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() - val bbevBottom = bbev.contentBottomOnScreen + bubblePositioner.insets.top activityScenario.onActivity { // notify that the IME top coordinate is greater than the bottom of the expanded view. // there's no overlap so it should not be clipped. - animationHelper.onImeTopChanged(bbevBottom * 2) + animationHelper.onImeTopChanged(bbev.contentBottomOnScreen * 2) } val outline = Outline() bbev.outlineProvider.getOutline(bbev, outline) diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml index 7347fbad5f5d..fc8b29912955 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml @@ -38,7 +38,7 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/bubble_popup_text_margin" android:maxWidth="@dimen/bubble_popup_content_max_width" - android:maxLines="1" + android:maxLines="2" android:ellipsize="end" android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline" android:textColor="@androidprv:color/materialColorOnSurface" diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml index f0e1871168dd..1616707954f5 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml @@ -38,7 +38,7 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/bubble_popup_text_margin" android:maxWidth="@dimen/bubble_popup_content_max_width" - android:maxLines="1" + android:maxLines="2" android:ellipsize="end" android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline" android:textColor="@androidprv:color/materialColorOnSurface" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java index 82ef00e46e8c..10023c9dba40 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java @@ -18,6 +18,7 @@ package com.android.wm.shell.appzoomout; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.systemui.Flags.spatialModelAppPushback; import android.app.ActivityManager; import android.app.WindowConfiguration; @@ -93,7 +94,9 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController mDisplayAreaOrganizer = displayAreaOrganizer; mMainExecutor = mainExecutor; - shellInit.addInitCallback(this::onInit, this); + if (spatialModelAppPushback()) { + shellInit.addInitCallback(this::onInit, this); + } } private void onInit() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt index f8f284238a98..8171312762ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt @@ -33,7 +33,7 @@ import android.view.WindowManager.TRANSIT_CHANGE import android.window.TransitionInfo import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction -import com.android.systemui.car.Flags.autoTaskStackWindowing +import com.android.wm.shell.Flags.enableAutoTaskStackController import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.ShellExecutor @@ -66,7 +66,7 @@ class AutoTaskStackControllerImpl @Inject constructor( private val defaultRootTaskPerDisplay = mutableMapOf<Int, Int>() init { - if (!autoTaskStackWindowing()) { + if (!enableAutoTaskStackController()) { throw IllegalStateException("Failed to initialize" + "AutoTaskStackController as the auto_task_stack_windowing TS flag is disabled.") } else { @@ -220,7 +220,7 @@ class AutoTaskStackControllerImpl @Inject constructor( displayId: Int, listener: RootTaskStackListener ) { - if (!autoTaskStackWindowing()) { + if (!enableAutoTaskStackController()) { Slog.e( TAG, "Failed to create root task stack as the " + "auto_task_stack_windowing TS flag is disabled." @@ -236,7 +236,7 @@ class AutoTaskStackControllerImpl @Inject constructor( } override fun setDefaultRootTaskStackOnDisplay(displayId: Int, rootTaskStackId: Int?) { - if (!autoTaskStackWindowing()) { + if (!enableAutoTaskStackController()) { Slog.e( TAG, "Failed to set default root task stack as the " + "auto_task_stack_windowing TS flag is disabled." @@ -280,7 +280,7 @@ class AutoTaskStackControllerImpl @Inject constructor( } override fun startTransition(transaction: AutoTaskStackTransaction): IBinder? { - if (!autoTaskStackWindowing()) { + if (!enableAutoTaskStackController()) { Slog.e( TAG, "Failed to start transaction as the " + "auto_task_stack_windowing TS flag is disabled." diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index 862906a11424..62995319db80 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -136,23 +136,15 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl // Update bitmap val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset) - bitmap = - iconFactory - .createBadgedIconBitmap(AdaptiveIconDrawable(ColorDrawable(colorAccent), fg)) - .icon + val drawable = AdaptiveIconDrawable(ColorDrawable(colorAccent), fg) + bitmap = iconFactory.createBadgedIconBitmap(drawable).icon // Update dot path dotPath = PathParser.createPathFromPathData( res.getString(com.android.internal.R.string.config_icon_mask) ) - val scale = - iconFactory.normalizer.getScale( - iconView!!.iconDrawable, - null /* outBounds */, - null /* path */, - null /* outMaskShape */ - ) + val scale = iconFactory.normalizer.getScale(iconView!!.iconDrawable) val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f val matrix = Matrix() matrix.setScale( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index e073b02dc630..ac5b9c9866ed 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -674,9 +674,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView if (mTaskView != null) { mTaskView.getBoundsOnScreen(mTempBounds); } - // return the bottom of the content rect, adjusted for insets so the result is in screen - // coordinate - return mTempBounds.bottom + mPositioner.getInsets().top; + return mTempBounds.bottom; } /** Update the amount by which to clip the expanded view at the bottom. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index c27ef295db51..c9136b4ad18d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -929,8 +929,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, for (int taskId : taskIds) { ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId); if (task != null) { - wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED) - .setBounds(task.token, null); + wct.setWindowingMode(task.getToken(), WINDOWING_MODE_UNDEFINED) + .setBounds(task.getToken(), null); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 81f444ba2af3..c5994f83429a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -633,7 +633,7 @@ public class SplashscreenContentDrawer { private class ShapeIconFactory extends BaseIconFactory { protected ShapeIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) { - super(context, fillResIconDpi, iconBitmapSize, true /* shapeDetection */); + super(context, fillResIconDpi, iconBitmapSize); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 9fbda46bd2b7..67dae283345a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -1969,7 +1969,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, Supplier<SurfaceControl.Transaction> transactionFactory, Handler handler) { final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled() - ? new VeiledResizeTaskPositioner( + // TODO(b/383632995): Update when the flag is launched. + ? (Flags.enableConnectedDisplaysWindowDrag() + ? new MultiDisplayVeiledResizeTaskPositioner( taskOrganizer, windowDecoration, displayController, @@ -1977,6 +1979,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, transitions, interactionJankMonitor, handler) + : new VeiledResizeTaskPositioner( + taskOrganizer, + windowDecoration, + displayController, + dragEventListener, + transitions, + interactionJankMonitor, + handler)) : new FluidResizeTaskPositioner( taskOrganizer, transitions, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt new file mode 100644 index 000000000000..8dc921c986ce --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2024 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.windowdecor + +import android.graphics.PointF +import android.graphics.Rect +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.view.Choreographer +import android.view.Surface +import android.view.SurfaceControl +import android.view.WindowManager +import android.window.TransitionInfo +import android.window.TransitionRequestInfo +import android.window.WindowContainerTransaction +import com.android.internal.jank.Cuj +import com.android.internal.jank.InteractionJankMonitor +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.transition.Transitions +import java.util.concurrent.TimeUnit +import java.util.function.Supplier + +/** + * A task positioner that also takes into account resizing a + * [com.android.wm.shell.windowdecor.ResizeVeil] and dragging move across multiple displays. + * - If the drag is resizing the task, we resize the veil instead. + * - If the drag is repositioning, we consider multi-display topology if needed, and update in the + * typical manner. + */ +class MultiDisplayVeiledResizeTaskPositioner( + private val taskOrganizer: ShellTaskOrganizer, + private val desktopWindowDecoration: DesktopModeWindowDecoration, + private val displayController: DisplayController, + dragEventListener: DragPositioningCallbackUtility.DragEventListener, + private val transactionSupplier: Supplier<SurfaceControl.Transaction>, + private val transitions: Transitions, + private val interactionJankMonitor: InteractionJankMonitor, + @ShellMainThread private val handler: Handler, +) : TaskPositioner, Transitions.TransitionHandler { + private val dragEventListeners = + mutableListOf<DragPositioningCallbackUtility.DragEventListener>() + private val stableBounds = Rect() + private val taskBoundsAtDragStart = Rect() + private val repositionStartPoint = PointF() + private val repositionTaskBounds = Rect() + private val isResizing: Boolean + get() = + (ctrlType and DragPositioningCallback.CTRL_TYPE_TOP) != 0 || + (ctrlType and DragPositioningCallback.CTRL_TYPE_BOTTOM) != 0 || + (ctrlType and DragPositioningCallback.CTRL_TYPE_LEFT) != 0 || + (ctrlType and DragPositioningCallback.CTRL_TYPE_RIGHT) != 0 + + @DragPositioningCallback.CtrlType private var ctrlType = 0 + private var isResizingOrAnimatingResize = false + @Surface.Rotation private var rotation = 0 + + constructor( + taskOrganizer: ShellTaskOrganizer, + windowDecoration: DesktopModeWindowDecoration, + displayController: DisplayController, + dragEventListener: DragPositioningCallbackUtility.DragEventListener, + transitions: Transitions, + interactionJankMonitor: InteractionJankMonitor, + @ShellMainThread handler: Handler, + ) : this( + taskOrganizer, + windowDecoration, + displayController, + dragEventListener, + Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() }, + transitions, + interactionJankMonitor, + handler, + ) + + init { + dragEventListeners.add(dragEventListener) + } + + override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect { + this.ctrlType = ctrlType + taskBoundsAtDragStart.set( + desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.bounds + ) + repositionStartPoint[x] = y + if (isResizing) { + // Capture CUJ for re-sizing window in DW mode. + interactionJankMonitor.begin( + createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW) + ) + if (!desktopWindowDecoration.mHasGlobalFocus) { + val wct = WindowContainerTransaction() + wct.reorder( + desktopWindowDecoration.mTaskInfo.token, + /* onTop= */ true, + /* includingParents= */ true, + ) + taskOrganizer.applyTransaction(wct) + } + } + for (dragEventListener in dragEventListeners) { + dragEventListener.onDragStart(desktopWindowDecoration.mTaskInfo.taskId) + } + repositionTaskBounds.set(taskBoundsAtDragStart) + val rotation = + desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.displayRotation + if (stableBounds.isEmpty || this.rotation != rotation) { + this.rotation = rotation + displayController + .getDisplayLayout(desktopWindowDecoration.mDisplay.displayId)!! + .getStableBounds(stableBounds) + } + return Rect(repositionTaskBounds) + } + + override fun onDragPositioningMove(displayId: Int, x: Float, y: Float): Rect { + check(Looper.myLooper() == handler.looper) { + "This method must run on the shell main thread." + } + val delta = DragPositioningCallbackUtility.calculateDelta(x, y, repositionStartPoint) + if ( + isResizing && + DragPositioningCallbackUtility.changeBounds( + ctrlType, + repositionTaskBounds, + taskBoundsAtDragStart, + stableBounds, + delta, + displayController, + desktopWindowDecoration, + ) + ) { + if (!isResizingOrAnimatingResize) { + for (dragEventListener in dragEventListeners) { + dragEventListener.onDragMove(desktopWindowDecoration.mTaskInfo.taskId) + } + desktopWindowDecoration.showResizeVeil(repositionTaskBounds) + isResizingOrAnimatingResize = true + } else { + desktopWindowDecoration.updateResizeVeil(repositionTaskBounds) + } + } else if (ctrlType == DragPositioningCallback.CTRL_TYPE_UNDEFINED) { + // Begin window drag CUJ instrumentation only when drag position moves. + interactionJankMonitor.begin( + createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW) + ) + val t = transactionSupplier.get() + DragPositioningCallbackUtility.setPositionOnDrag( + desktopWindowDecoration, + repositionTaskBounds, + taskBoundsAtDragStart, + repositionStartPoint, + t, + x, + y, + ) + t.setFrameTimeline(Choreographer.getInstance().vsyncId) + t.apply() + } + return Rect(repositionTaskBounds) + } + + override fun onDragPositioningEnd(displayId: Int, x: Float, y: Float): Rect { + val delta = DragPositioningCallbackUtility.calculateDelta(x, y, repositionStartPoint) + if (isResizing) { + if (taskBoundsAtDragStart != repositionTaskBounds) { + DragPositioningCallbackUtility.changeBounds( + ctrlType, + repositionTaskBounds, + taskBoundsAtDragStart, + stableBounds, + delta, + displayController, + desktopWindowDecoration, + ) + desktopWindowDecoration.updateResizeVeil(repositionTaskBounds) + val wct = WindowContainerTransaction() + wct.setBounds(desktopWindowDecoration.mTaskInfo.token, repositionTaskBounds) + transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, this) + } else { + // If bounds haven't changed, perform necessary veil reset here as startAnimation + // won't be called. + resetVeilIfVisible() + } + interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW) + } else { + DragPositioningCallbackUtility.updateTaskBounds( + repositionTaskBounds, + taskBoundsAtDragStart, + repositionStartPoint, + x, + y, + ) + interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW) + } + + ctrlType = DragPositioningCallback.CTRL_TYPE_UNDEFINED + taskBoundsAtDragStart.setEmpty() + repositionStartPoint[0f] = 0f + return Rect(repositionTaskBounds) + } + + private fun resetVeilIfVisible() { + if (isResizingOrAnimatingResize) { + desktopWindowDecoration.hideResizeVeil() + isResizingOrAnimatingResize = false + } + } + + private fun createLongTimeoutJankConfigBuilder(@Cuj.CujType cujType: Int) = + InteractionJankMonitor.Configuration.Builder.withSurface( + cujType, + desktopWindowDecoration.mContext, + desktopWindowDecoration.mTaskSurface, + handler, + ) + .setTimeout(LONG_CUJ_TIMEOUT_MS) + + override fun startAnimation( + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + finishCallback: Transitions.TransitionFinishCallback, + ): Boolean { + for (change in info.changes) { + val sc = change.leash + val endBounds = change.endAbsBounds + val endPosition = change.endRelOffset + startTransaction + .setWindowCrop(sc, endBounds.width(), endBounds.height()) + .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat()) + finishTransaction + .setWindowCrop(sc, endBounds.width(), endBounds.height()) + .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat()) + } + + startTransaction.apply() + resetVeilIfVisible() + ctrlType = DragPositioningCallback.CTRL_TYPE_UNDEFINED + finishCallback.onTransitionFinished(null /* wct */) + isResizingOrAnimatingResize = false + interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW) + return true + } + + /** + * We should never reach this as this handler's transitions are only started from shell + * explicitly. + */ + override fun handleRequest( + transition: IBinder, + request: TransitionRequestInfo, + ): WindowContainerTransaction? { + return null + } + + override fun isResizingOrAnimating() = isResizingOrAnimatingResize + + override fun addDragEventListener( + dragEventListener: DragPositioningCallbackUtility.DragEventListener + ) { + dragEventListeners.add(dragEventListener) + } + + override fun removeDragEventListener( + dragEventListener: DragPositioningCallbackUtility.DragEventListener + ) { + dragEventListeners.remove(dragEventListener) + } + + companion object { + // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid + // timing out in the middle of a resize or drag action. + private val LONG_CUJ_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(/* duration= */ 10L) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index e011cc08903b..d2c79d76e6c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -53,7 +53,12 @@ import java.util.function.Supplier; * {@link com.android.wm.shell.windowdecor.ResizeVeil}. * If the drag is resizing the task, we resize the veil instead. * If the drag is repositioning, we update in the typical manner. + * <p> + * @deprecated This class will be replaced by + * {@link com.android.wm.shell.windowdecor.MultiDisplayVeiledResizeTaskPositioner}. + * TODO(b/383632995): Remove this class after MultiDisplayVeiledResizeTaskPositioner is launched. */ +@Deprecated public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.TransitionHandler { // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid // timing out in the middle of a resize or drag action. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 2d0ea5fdc884..7a88ace3f85f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -17,6 +17,7 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED; @@ -32,6 +33,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.atLeastOnce; @@ -50,9 +53,11 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.view.SurfaceControl; import android.window.RemoteTransition; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.test.annotation.UiThreadTest; @@ -84,6 +89,7 @@ import com.android.wm.shell.transition.Transitions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -425,6 +431,30 @@ public class StageCoordinatorTests extends ShellTestCase { .startFullscreenTransition(any(), any()); } + + @Test + public void startTask_ensureWindowingModeCleared() { + SplitScreenTransitions splitScreenTransitions = + spy(mStageCoordinator.getSplitTransitions()); + mStageCoordinator.setSplitTransitions(splitScreenTransitions); + ArgumentCaptor<WindowContainerTransaction> wctCaptor = + ArgumentCaptor.forClass(WindowContainerTransaction.class); + int taskId = 18; + IBinder binder = mock(IBinder.class); + ActivityManager.RunningTaskInfo rti = mock(ActivityManager.RunningTaskInfo.class); + WindowContainerToken mockToken = mock(WindowContainerToken.class); + when(mockToken.asBinder()).thenReturn(binder); + when(rti.getToken()).thenReturn(mockToken); + when(mTaskOrganizer.getRunningTaskInfo(taskId)).thenReturn(rti); + mStageCoordinator.startTask(taskId, SPLIT_POSITION_TOP_OR_LEFT, null /*options*/, + null, SPLIT_INDEX_UNDEFINED); + verify(splitScreenTransitions).startEnterTransition(anyInt(), + wctCaptor.capture(), any(), any(), anyInt(), anyBoolean()); + + int windowingMode = wctCaptor.getValue().getChanges().get(binder).getWindowingMode(); + assertEquals(windowingMode, WINDOWING_MODE_UNDEFINED); + } + private Transitions createTestTransitions() { ShellInit shellInit = new ShellInit(mMainExecutor); final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class), diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt new file mode 100644 index 000000000000..f179cac32244 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt @@ -0,0 +1,611 @@ +/* + * Copyright (C) 2024 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.windowdecor + +import android.app.ActivityManager +import android.app.WindowConfiguration +import android.content.Context +import android.content.res.Resources +import android.graphics.Point +import android.graphics.Rect +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.testing.AndroidTestingRunner +import android.view.Display +import android.view.Surface.ROTATION_0 +import android.view.Surface.ROTATION_270 +import android.view.Surface.ROTATION_90 +import android.view.SurfaceControl +import android.view.SurfaceControl.Transaction +import android.view.WindowManager.TRANSIT_CHANGE +import android.window.TransitionInfo +import android.window.WindowContainerToken +import androidx.test.filters.SmallTest +import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread +import com.android.internal.jank.InteractionJankMonitor +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.TransitionFinishCallback +import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM +import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT +import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP +import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED +import java.util.function.Supplier +import junit.framework.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.argThat +import org.mockito.Mockito.eq +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +/** + * Tests for [MultiDisplayVeiledResizeTaskPositioner]. + * + * Build/Install/Run: atest WMShellUnitTests:MultiDisplayVeiledResizeTaskPositionerTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { + + @Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer + @Mock private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration + @Mock + private lateinit var mockDragEventListener: DragPositioningCallbackUtility.DragEventListener + + @Mock private lateinit var taskToken: WindowContainerToken + @Mock private lateinit var taskBinder: IBinder + + @Mock private lateinit var mockDisplayController: DisplayController + @Mock private lateinit var mockDisplayLayout: DisplayLayout + @Mock private lateinit var mockDisplay: Display + @Mock private lateinit var mockTransactionFactory: Supplier<SurfaceControl.Transaction> + @Mock private lateinit var mockTransaction: SurfaceControl.Transaction + @Mock private lateinit var mockTransitionBinder: IBinder + @Mock private lateinit var mockTransitionInfo: TransitionInfo + @Mock private lateinit var mockFinishCallback: TransitionFinishCallback + @Mock private lateinit var mockTransitions: Transitions + @Mock private lateinit var mockContext: Context + @Mock private lateinit var mockResources: Resources + @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor + private val mainHandler = Handler(Looper.getMainLooper()) + + private lateinit var taskPositioner: MultiDisplayVeiledResizeTaskPositioner + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + mockDesktopWindowDecoration.mDisplay = mockDisplay + mockDesktopWindowDecoration.mDecorWindowContext = mockContext + whenever(mockContext.getResources()).thenReturn(mockResources) + whenever(taskToken.asBinder()).thenReturn(taskBinder) + whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout) + whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI) + whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> + if ( + mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration + .displayRotation == ROTATION_90 || + mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration + .displayRotation == ROTATION_270 + ) { + (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE) + } else { + (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT) + } + } + `when`(mockTransactionFactory.get()).thenReturn(mockTransaction) + mockDesktopWindowDecoration.mTaskInfo = + ActivityManager.RunningTaskInfo().apply { + taskId = TASK_ID + token = taskToken + minWidth = MIN_WIDTH + minHeight = MIN_HEIGHT + defaultMinSize = DEFAULT_MIN + displayId = DISPLAY_ID + configuration.windowConfiguration.setBounds(STARTING_BOUNDS) + configuration.windowConfiguration.displayRotation = ROTATION_90 + isResizeable = true + } + `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA) + mockDesktopWindowDecoration.mDisplay = mockDisplay + whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID } + + taskPositioner = + MultiDisplayVeiledResizeTaskPositioner( + mockShellTaskOrganizer, + mockDesktopWindowDecoration, + mockDisplayController, + mockDragEventListener, + mockTransactionFactory, + mockTransitions, + mockInteractionJankMonitor, + mainHandler, + ) + } + + @Test + fun testDragResize_noMove_doesNotShowResizeVeil() = runOnUiThread { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, + DISPLAY_ID, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat(), + ) + verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS) + + taskPositioner.onDragPositioningEnd( + DISPLAY_ID, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat(), + ) + + verify(mockTransitions, never()) + .startTransition( + eq(TRANSIT_CHANGE), + argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != + 0 && + change.configuration.windowConfiguration.bounds == STARTING_BOUNDS + } + }, + eq(taskPositioner), + ) + verify(mockDesktopWindowDecoration, never()).hideResizeVeil() + } + + @Test + fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_UNDEFINED, + DISPLAY_ID, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat(), + ) + + taskPositioner.onDragPositioningMove( + DISPLAY_ID, + STARTING_BOUNDS.left.toFloat() + 60, + STARTING_BOUNDS.top.toFloat() + 100, + ) + val rectAfterMove = Rect(STARTING_BOUNDS) + rectAfterMove.left += 60 + rectAfterMove.right += 60 + rectAfterMove.top += 100 + rectAfterMove.bottom += 100 + verify(mockTransaction) + .setPosition(any(), eq(rectAfterMove.left.toFloat()), eq(rectAfterMove.top.toFloat())) + + val endBounds = + taskPositioner.onDragPositioningEnd( + DISPLAY_ID, + STARTING_BOUNDS.left.toFloat() + 70, + STARTING_BOUNDS.top.toFloat() + 20, + ) + val rectAfterEnd = Rect(STARTING_BOUNDS) + rectAfterEnd.left += 70 + rectAfterEnd.right += 70 + rectAfterEnd.top += 20 + rectAfterEnd.bottom += 20 + + verify(mockDesktopWindowDecoration, never()).showResizeVeil(any()) + verify(mockDesktopWindowDecoration, never()).hideResizeVeil() + Assert.assertEquals(rectAfterEnd, endBounds) + } + + @Test + fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, + DISPLAY_ID, + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.top.toFloat(), + ) + + taskPositioner.onDragPositioningMove( + DISPLAY_ID, + STARTING_BOUNDS.right.toFloat() + 10, + STARTING_BOUNDS.top.toFloat() + 10, + ) + + val rectAfterMove = Rect(STARTING_BOUNDS) + rectAfterMove.right += 10 + rectAfterMove.top += 10 + verify(mockDesktopWindowDecoration).showResizeVeil(rectAfterMove) + verify(mockShellTaskOrganizer, never()) + .applyTransaction( + argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != + 0 && + change.configuration.windowConfiguration.bounds == rectAfterMove + } + } + ) + + taskPositioner.onDragPositioningEnd( + DISPLAY_ID, + STARTING_BOUNDS.right.toFloat() + 20, + STARTING_BOUNDS.top.toFloat() + 20, + ) + val rectAfterEnd = Rect(rectAfterMove) + rectAfterEnd.right += 10 + rectAfterEnd.top += 10 + verify(mockDesktopWindowDecoration).updateResizeVeil(any()) + verify(mockTransitions) + .startTransition( + eq(TRANSIT_CHANGE), + argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != + 0 && + change.configuration.windowConfiguration.bounds == rectAfterEnd + } + }, + eq(taskPositioner), + ) + } + + @Test + fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() = runOnUiThread { + taskPositioner.onDragPositioningStart( + DISPLAY_ID, + CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat(), + ) + + taskPositioner.onDragPositioningMove( + DISPLAY_ID, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat(), + ) + + taskPositioner.onDragPositioningEnd( + DISPLAY_ID, + STARTING_BOUNDS.left.toFloat() + 10, + STARTING_BOUNDS.top.toFloat() + 10, + ) + + verify(mockTransitions, never()) + .startTransition( + eq(TRANSIT_CHANGE), + argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != + 0 && + change.configuration.windowConfiguration.bounds == STARTING_BOUNDS + } + }, + eq(taskPositioner), + ) + + verify(mockShellTaskOrganizer, never()) + .applyTransaction( + argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != + 0) + } + } + ) + } + + @Test + fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_UNDEFINED, // drag + DISPLAY_ID, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat(), + ) + + val newX = STARTING_BOUNDS.left.toFloat() + 5 + val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1 + taskPositioner.onDragPositioningMove(DISPLAY_ID, newX, newY) + + taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY) + + verify(mockShellTaskOrganizer, never()) + .applyTransaction( + argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != + 0) + } + } + ) + } + + @Test + fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() = runOnUiThread { + mockDesktopWindowDecoration.mHasGlobalFocus = false + taskPositioner.onDragPositioningStart( + CTRL_TYPE_RIGHT, // Resize right + DISPLAY_ID, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat(), + ) + + // Verify task is reordered to top + verify(mockShellTaskOrganizer) + .applyTransaction( + argThat { wct -> + return@argThat wct.hierarchyOps.any { hierarchyOps -> + hierarchyOps.container == taskBinder && hierarchyOps.toTop + } + } + ) + } + + @Test + fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() = runOnUiThread { + mockDesktopWindowDecoration.mHasGlobalFocus = true + taskPositioner.onDragPositioningStart( + CTRL_TYPE_RIGHT, // Resize right + DISPLAY_ID, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat(), + ) + + // Verify task is not reordered to top + verify(mockShellTaskOrganizer, never()) + .applyTransaction( + argThat { wct -> + return@argThat wct.hierarchyOps.any { hierarchyOps -> + hierarchyOps.container == taskBinder && hierarchyOps.toTop + } + } + ) + } + + @Test + fun testDragResize_drag_draggedTaskNotReorderedToTop() = runOnUiThread { + mockDesktopWindowDecoration.mHasGlobalFocus = false + taskPositioner.onDragPositioningStart( + CTRL_TYPE_UNDEFINED, // drag + DISPLAY_ID, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat(), + ) + + // Verify task is not reordered to top since task is already brought to top before dragging + // begins + verify(mockShellTaskOrganizer, never()) + .applyTransaction( + argThat { wct -> + return@argThat wct.hierarchyOps.any { hierarchyOps -> + hierarchyOps.container == taskBinder && hierarchyOps.toTop + } + } + ) + } + + @Test + fun testDragResize_drag_updatesStableBoundsOnRotate() = runOnUiThread { + // Test landscape stable bounds + performDrag( + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.bottom.toFloat(), + STARTING_BOUNDS.right.toFloat() + 2000, + STARTING_BOUNDS.bottom.toFloat() + 2000, + CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, + ) + val rectAfterDrag = Rect(STARTING_BOUNDS) + rectAfterDrag.right += 2000 + rectAfterDrag.bottom = STABLE_BOUNDS_LANDSCAPE.bottom + // First drag; we should fetch stable bounds. + verify(mockDisplayLayout, times(1)).getStableBounds(any()) + verify(mockTransitions) + .startTransition( + eq(TRANSIT_CHANGE), + argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != + 0 && + change.configuration.windowConfiguration.bounds == rectAfterDrag + } + }, + eq(taskPositioner), + ) + // Drag back to starting bounds. + performDrag( + STARTING_BOUNDS.right.toFloat() + 2000, + STARTING_BOUNDS.bottom.toFloat(), + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.bottom.toFloat(), + CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, + ) + + // Display did not rotate; we should use previous stable bounds + verify(mockDisplayLayout, times(1)).getStableBounds(any()) + + // Rotate the screen to portrait + mockDesktopWindowDecoration.mTaskInfo.apply { + configuration.windowConfiguration.displayRotation = ROTATION_0 + } + // Test portrait stable bounds + performDrag( + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.bottom.toFloat(), + STARTING_BOUNDS.right.toFloat() + 2000, + STARTING_BOUNDS.bottom.toFloat() + 2000, + CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, + ) + rectAfterDrag.right = STABLE_BOUNDS_PORTRAIT.right + rectAfterDrag.bottom = STARTING_BOUNDS.bottom + 2000 + + verify(mockTransitions) + .startTransition( + eq(TRANSIT_CHANGE), + argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != + 0 && + change.configuration.windowConfiguration.bounds == rectAfterDrag + } + }, + eq(taskPositioner), + ) + // Display has rotated; we expect a new stable bounds. + verify(mockDisplayLayout, times(2)).getStableBounds(any()) + } + + @Test + fun testIsResizingOrAnimatingResizeSet() = runOnUiThread { + Assert.assertFalse(taskPositioner.isResizingOrAnimating) + + taskPositioner.onDragPositioningStart( + CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, + DISPLAY_ID, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat(), + ) + + taskPositioner.onDragPositioningMove( + DISPLAY_ID, + STARTING_BOUNDS.left.toFloat() - 20, + STARTING_BOUNDS.top.toFloat() - 20, + ) + + // isResizingOrAnimating should be set to true after move during a resize + Assert.assertTrue(taskPositioner.isResizingOrAnimating) + verify(mockDragEventListener, times(1)).onDragMove(eq(TASK_ID)) + + taskPositioner.onDragPositioningEnd( + DISPLAY_ID, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat(), + ) + + // isResizingOrAnimating should be not be set till false until after transition animation + Assert.assertTrue(taskPositioner.isResizingOrAnimating) + } + + @Test + fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() = runOnUiThread { + performDrag( + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat(), + STARTING_BOUNDS.left.toFloat() - 20, + STARTING_BOUNDS.top.toFloat() - 20, + CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, + ) + + taskPositioner.startAnimation( + mockTransitionBinder, + mockTransitionInfo, + mockTransaction, + mockTransaction, + mockFinishCallback, + ) + + // isResizingOrAnimating should be set to false until after transition successfully consumed + Assert.assertFalse(taskPositioner.isResizingOrAnimating) + } + + @Test + fun testStartAnimation_useEndRelOffset() = runOnUiThread { + val changeMock = mock(TransitionInfo.Change::class.java) + val startTransaction = mock(Transaction::class.java) + val finishTransaction = mock(Transaction::class.java) + val point = Point(10, 20) + val bounds = Rect(1, 2, 3, 4) + `when`(changeMock.leash).thenReturn(mock(SurfaceControl::class.java)) + `when`(changeMock.endRelOffset).thenReturn(point) + `when`(changeMock.endAbsBounds).thenReturn(bounds) + `when`(mockTransitionInfo.changes).thenReturn(listOf(changeMock)) + `when`(startTransaction.setWindowCrop(any(), eq(bounds.width()), eq(bounds.height()))) + .thenReturn(startTransaction) + `when`(finishTransaction.setWindowCrop(any(), eq(bounds.width()), eq(bounds.height()))) + .thenReturn(finishTransaction) + + taskPositioner.startAnimation( + mockTransitionBinder, + mockTransitionInfo, + startTransaction, + finishTransaction, + mockFinishCallback, + ) + + verify(startTransaction).setPosition(any(), eq(point.x.toFloat()), eq(point.y.toFloat())) + verify(finishTransaction).setPosition(any(), eq(point.x.toFloat()), eq(point.y.toFloat())) + verify(changeMock).endRelOffset + } + + private fun performDrag(startX: Float, startY: Float, endX: Float, endY: Float, ctrlType: Int) { + taskPositioner.onDragPositioningStart(ctrlType, DISPLAY_ID, startX, startY) + taskPositioner.onDragPositioningMove(DISPLAY_ID, endX, endY) + + taskPositioner.onDragPositioningEnd(DISPLAY_ID, endX, endY) + } + + companion object { + private const val TASK_ID = 5 + private const val MIN_WIDTH = 10 + private const val MIN_HEIGHT = 10 + private const val DENSITY_DPI = 20 + private const val DEFAULT_MIN = 40 + private const val DISPLAY_ID = 1 + private const val NAVBAR_HEIGHT = 50 + private const val CAPTION_HEIGHT = 50 + private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10 + private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) + private val STARTING_BOUNDS = Rect(100, 100, 200, 200) + private val STABLE_BOUNDS_LANDSCAPE = + Rect( + DISPLAY_BOUNDS.left, + DISPLAY_BOUNDS.top + CAPTION_HEIGHT, + DISPLAY_BOUNDS.right, + DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT, + ) + private val STABLE_BOUNDS_PORTRAIT = + Rect( + DISPLAY_BOUNDS.top, + DISPLAY_BOUNDS.left + CAPTION_HEIGHT, + DISPLAY_BOUNDS.bottom, + DISPLAY_BOUNDS.right - NAVBAR_HEIGHT, + ) + private val VALID_DRAG_AREA = + Rect( + DISPLAY_BOUNDS.left - 100, + STABLE_BOUNDS_LANDSCAPE.top, + DISPLAY_BOUNDS.right - 100, + DISPLAY_BOUNDS.bottom - 100, + ) + } +} diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index f42017dc835a..3104f9d42891 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -503,7 +503,10 @@ public abstract class MediaRoute2ProviderService extends Service { String sessionId = sessionInfo.getId(); synchronized (mSessionLock) { - if (mSessionInfos.containsKey(sessionId)) { + var mediaStreams = mOngoingMediaStreams.get(sessionId); + if (Flags.enableMirroringInMediaRouter2() && mediaStreams != null) { + mediaStreams.mSessionInfo = sessionInfo; + } else if (mSessionInfos.containsKey(sessionId)) { mSessionInfos.put(sessionId, sessionInfo); } else { Log.w(TAG, "notifySessionUpdated: Ignoring unknown session info."); @@ -836,6 +839,9 @@ public abstract class MediaRoute2ProviderService extends Service { List<RoutingSessionInfo> sessions; synchronized (mSessionLock) { sessions = new ArrayList<>(mSessionInfos.values()); + if (Flags.enableMirroringInMediaRouter2()) { + mOngoingMediaStreams.values().forEach(it -> sessions.add(it.mSessionInfo)); + } } try { @@ -888,7 +894,13 @@ public abstract class MediaRoute2ProviderService extends Service { Log.w(TAG, description + ": Ignoring empty sessionId from system service."); return false; } - if (getSessionInfo(sessionId) == null) { + boolean idMatchesSystemSession = false; + if (Flags.enableMirroringInMediaRouter2()) { + synchronized (mSessionLock) { + idMatchesSystemSession = mOngoingMediaStreams.containsKey(sessionId); + } + } + if (!idMatchesSystemSession && getSessionInfo(sessionId) == null) { Log.w(TAG, description + ": Ignoring unknown session from system service. " + "sessionId=" + sessionId); return false; @@ -1079,8 +1091,8 @@ public abstract class MediaRoute2ProviderService extends Service { * * @hide */ - @GuardedBy("MediaRoute2ProviderService.this.mSessionLock") @NonNull + // Access guarded by mSessionsLock, but it's not convenient to enforce through @GuardedBy. private RoutingSessionInfo mSessionInfo; // TODO: b/380431086: Add the video equivalent. diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig index 3451dfc559ee..28da55656177 100644 --- a/media/java/android/media/tv/flags/media_tv.aconfig +++ b/media/java/android/media/tv/flags/media_tv.aconfig @@ -120,3 +120,13 @@ flag { description: "Collect physical address from HDMI-CEC messages in metrics" bug: "376001043" } + +flag { + name: "tif_extension_standardization_bugfix" + namespace: "tv_os" + description: "Bug fix flag for standardizing AIDL extension interface of TIS" + bug: "389779152" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 2fe069af638a..bf330dab266c 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -701,6 +701,9 @@ void FilterClientCallbackImpl::getMediaEvent(const jobjectArray& arr, const int // Protect mFilterClient from being set to null. android::Mutex::Autolock autoLock(mLock); + if (mFilterClient == nullptr) { + return; + } uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size; if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 || (dataLength > 0 && (dataLength + offset) < avSharedMemSize)) { @@ -868,10 +871,18 @@ void FilterClientCallbackImpl::getRestartEvent(const jobjectArray& arr, const in void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &events) { ALOGV("FilterClientCallbackImpl::onFilterEvent"); JNIEnv *env = AndroidRuntime::getJNIEnv(); + ScopedLocalRef<jobjectArray> array(env); if (!events.empty()) { array.reset(env->NewObjectArray(events.size(), mEventClass, nullptr)); + if (env->IsSameObject(array.get(), nullptr)) { + // It can happen when FilterClientCallbackImpl release the resource + // in another thread. + ALOGE("FilterClientCallbackImpl::onFilterEvent:" + "Unable to create object array of filter events. Ignoring callback."); + return; + } } for (int i = 0, arraySize = 0; i < events.size(); i++) { @@ -1070,14 +1081,15 @@ FilterClientCallbackImpl::FilterClientCallbackImpl() { FilterClientCallbackImpl::~FilterClientCallbackImpl() { JNIEnv *env = AndroidRuntime::getJNIEnv(); - { - android::Mutex::Autolock autoLock(mLock); - if (mFilterObj != nullptr) { - env->DeleteWeakGlobalRef(mFilterObj); - mFilterObj = nullptr; - } - mFilterClient = nullptr; + + android::Mutex::Autolock autoLock(mLock); + + if (mFilterObj != nullptr) { + env->DeleteWeakGlobalRef(mFilterObj); + mFilterObj = nullptr; } + mFilterClient = nullptr; + env->DeleteGlobalRef(mEventClass); env->DeleteGlobalRef(mSectionEventClass); env->DeleteGlobalRef(mMediaEventClass); diff --git a/native/android/TEST_MAPPING b/native/android/TEST_MAPPING index e40af595f248..70560a84b88e 100644 --- a/native/android/TEST_MAPPING +++ b/native/android/TEST_MAPPING @@ -24,22 +24,6 @@ { "name": "NativeThermalUnitTestCases", "file_patterns": ["thermal.cpp"] - }, - { - "file_patterns": ["system_health.cpp"], - "name": "NativeSystemHealthUnitTestCases", - "options": [ - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] - }, - { - "file_patterns": ["system_health.cpp"], - "name": "CtsSystemHealthTestCases", - "options": [ - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] } ] } diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp index 4180710534c3..bc273c2d0833 100644 --- a/native/graphics/jni/Android.bp +++ b/native/graphics/jni/Android.bp @@ -102,8 +102,13 @@ cc_defaults { static_libs: ["libarect"], fuzz_config: { cc: [ + // Alphabetical order -- assign to skia-android-triage@google.com + "danieldilan@google.com", "dichenzhang@google.com", - "scroggo@google.com", + "fmalita@google.com", + "jreck@google.com", + "nscobie@google.com", + "skia-android-triage@google.com", ], asan_options: [ "detect_odr_violation=1", diff --git a/nfc-non-updatable/flags/flags.aconfig b/nfc-non-updatable/flags/flags.aconfig index 6b14a1ed3990..54ded0cddffa 100644 --- a/nfc-non-updatable/flags/flags.aconfig +++ b/nfc-non-updatable/flags/flags.aconfig @@ -197,3 +197,11 @@ flag { description: "Expose constructor for ApduServiceInfo" bug: "380892385" } + +flag { + name: "nfc_hce_latency_events" + is_exported: true + namespace: "wallet_integration" + description: "Enables tracking latency for HCE" + bug: "379849603" +} diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java b/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java deleted file mode 100644 index fdb0fc538fdf..000000000000 --- a/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright (C) 2019 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.service.watchdog; - -import static android.os.Parcelable.Creator; - -import android.annotation.CallbackExecutor; -import android.annotation.FlaggedApi; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SdkConstant; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.app.Service; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.crashrecovery.flags.Flags; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.RemoteCallback; -import android.os.RemoteException; -import android.util.Log; - -import com.android.internal.util.Preconditions; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; - -/** - * A service to provide packages supporting explicit health checks and route checks to these - * packages on behalf of the package watchdog. - * - * <p>To extend this class, you must declare the service in your manifest file with the - * {@link android.Manifest.permission#BIND_EXPLICIT_HEALTH_CHECK_SERVICE} permission, - * and include an intent filter with the {@link #SERVICE_INTERFACE} action. In adddition, - * your implementation must live in - * {@link PackageManager#getServicesSystemSharedLibraryPackageName()}. - * For example:</p> - * <pre> - * <service android:name=".FooExplicitHealthCheckService" - * android:exported="true" - * android:priority="100" - * android:permission="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"> - * <intent-filter> - * <action android:name="android.service.watchdog.ExplicitHealthCheckService" /> - * </intent-filter> - * </service> - * </pre> - * @hide - */ -@SystemApi -public abstract class ExplicitHealthCheckService extends Service { - - private static final String TAG = "ExplicitHealthCheckService"; - - /** - * {@link Bundle} key for a {@link List} of {@link PackageConfig} value. - * - * {@hide} - */ - public static final String EXTRA_SUPPORTED_PACKAGES = - "android.service.watchdog.extra.supported_packages"; - - /** - * {@link Bundle} key for a {@link List} of {@link String} value. - * - * {@hide} - */ - public static final String EXTRA_REQUESTED_PACKAGES = - "android.service.watchdog.extra.requested_packages"; - - /** - * {@link Bundle} key for a {@link String} value. - */ - @FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY) - public static final String EXTRA_HEALTH_CHECK_PASSED_PACKAGE = - "android.service.watchdog.extra.HEALTH_CHECK_PASSED_PACKAGE"; - - /** - * The Intent action that a service must respond to. Add it to the intent filter of the service - * in its manifest. - */ - @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) - public static final String SERVICE_INTERFACE = - "android.service.watchdog.ExplicitHealthCheckService"; - - /** - * The permission that a service must require to ensure that only Android system can bind to it. - * If this permission is not enforced in the AndroidManifest of the service, the system will - * skip that service. - */ - public static final String BIND_PERMISSION = - "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"; - - private final ExplicitHealthCheckServiceWrapper mWrapper = - new ExplicitHealthCheckServiceWrapper(); - - /** - * Called when the system requests an explicit health check for {@code packageName}. - * - * <p> When {@code packageName} passes the check, implementors should call - * {@link #notifyHealthCheckPassed} to inform the system. - * - * <p> It could take many hours before a {@code packageName} passes a check and implementors - * should never drop requests unless {@link onCancel} is called or the service dies. - * - * <p> Requests should not be queued and additional calls while expecting a result for - * {@code packageName} should have no effect. - */ - public abstract void onRequestHealthCheck(@NonNull String packageName); - - /** - * Called when the system cancels the explicit health check request for {@code packageName}. - * Should do nothing if there are is no active request for {@code packageName}. - */ - public abstract void onCancelHealthCheck(@NonNull String packageName); - - /** - * Called when the system requests for all the packages supporting explicit health checks. The - * system may request an explicit health check for any of these packages with - * {@link #onRequestHealthCheck}. - * - * @return all packages supporting explicit health checks - */ - @NonNull public abstract List<PackageConfig> onGetSupportedPackages(); - - /** - * Called when the system requests for all the packages that it has currently requested - * an explicit health check for. - * - * @return all packages expecting an explicit health check result - */ - @NonNull public abstract List<String> onGetRequestedPackages(); - - private final Handler mHandler = Handler.createAsync(Looper.getMainLooper()); - @Nullable private Consumer<Bundle> mHealthCheckResultCallback; - @Nullable private Executor mCallbackExecutor; - - @Override - @NonNull - public final IBinder onBind(@NonNull Intent intent) { - return mWrapper; - } - - /** - * Sets a callback to be invoked when an explicit health check passes for a package. - * <p> - * The callback will receive a {@link Bundle} containing the package name that passed the - * health check, identified by the key {@link #EXTRA_HEALTH_CHECK_PASSED_PACKAGE}. - * <p> - * <b>Note:</b> This API is primarily intended for testing purposes. Calling this outside of a - * test environment will override the default callback mechanism used to notify the system - * about health check results. Use with caution in production code. - * - * @param executor The executor on which the callback should be invoked. If {@code null}, the - * callback will be executed on the main thread. - * @param callback A callback that receives a {@link Bundle} containing the package name that - * passed the health check. - */ - @FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY) - public final void setHealthCheckPassedCallback(@CallbackExecutor @Nullable Executor executor, - @Nullable Consumer<Bundle> callback) { - mCallbackExecutor = executor; - mHealthCheckResultCallback = callback; - } - - private void executeCallback(@NonNull String packageName) { - if (mHealthCheckResultCallback != null) { - Objects.requireNonNull(packageName, - "Package passing explicit health check must be non-null"); - Bundle bundle = new Bundle(); - bundle.putString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE, packageName); - mHealthCheckResultCallback.accept(bundle); - } else { - Log.wtf(TAG, "System missed explicit health check result for " + packageName); - } - } - - /** - * Implementors should call this to notify the system when explicit health check passes - * for {@code packageName}; - */ - public final void notifyHealthCheckPassed(@NonNull String packageName) { - if (mCallbackExecutor != null) { - mCallbackExecutor.execute(() -> executeCallback(packageName)); - } else { - mHandler.post(() -> executeCallback(packageName)); - } - } - - /** - * A PackageConfig contains a package supporting explicit health checks and the - * timeout in {@link System#uptimeMillis} across reboots after which health - * check requests from clients are failed. - * - * @hide - */ - @SystemApi - public static final class PackageConfig implements Parcelable { - private static final long DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(1); - - private final String mPackageName; - private final long mHealthCheckTimeoutMillis; - - /** - * Creates a new instance. - * - * @param packageName the package name - * @param durationMillis the duration in milliseconds, must be greater than or - * equal to 0. If it is 0, it will use a system default value. - */ - public PackageConfig(@NonNull String packageName, long healthCheckTimeoutMillis) { - mPackageName = Preconditions.checkNotNull(packageName); - if (healthCheckTimeoutMillis == 0) { - mHealthCheckTimeoutMillis = DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS; - } else { - mHealthCheckTimeoutMillis = Preconditions.checkArgumentNonnegative( - healthCheckTimeoutMillis); - } - } - - private PackageConfig(Parcel parcel) { - mPackageName = parcel.readString(); - mHealthCheckTimeoutMillis = parcel.readLong(); - } - - /** - * Gets the package name. - * - * @return the package name - */ - public @NonNull String getPackageName() { - return mPackageName; - } - - /** - * Gets the timeout in milliseconds to evaluate an explicit health check result after a - * request. - * - * @return the duration in {@link System#uptimeMillis} across reboots - */ - public long getHealthCheckTimeoutMillis() { - return mHealthCheckTimeoutMillis; - } - - @NonNull - @Override - public String toString() { - return "PackageConfig{" + mPackageName + ", " + mHealthCheckTimeoutMillis + "}"; - } - - @Override - public boolean equals(@Nullable Object other) { - if (other == this) { - return true; - } - if (!(other instanceof PackageConfig)) { - return false; - } - - PackageConfig otherInfo = (PackageConfig) other; - return Objects.equals(otherInfo.getHealthCheckTimeoutMillis(), - mHealthCheckTimeoutMillis) - && Objects.equals(otherInfo.getPackageName(), mPackageName); - } - - @Override - public int hashCode() { - return Objects.hash(mPackageName, mHealthCheckTimeoutMillis); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@SuppressLint({"MissingNullability"}) Parcel parcel, int flags) { - parcel.writeString(mPackageName); - parcel.writeLong(mHealthCheckTimeoutMillis); - } - - public static final @NonNull Creator<PackageConfig> CREATOR = new Creator<PackageConfig>() { - @Override - public PackageConfig createFromParcel(Parcel source) { - return new PackageConfig(source); - } - - @Override - public PackageConfig[] newArray(int size) { - return new PackageConfig[size]; - } - }; - } - - - private class ExplicitHealthCheckServiceWrapper extends IExplicitHealthCheckService.Stub { - @Override - public void setCallback(RemoteCallback callback) throws RemoteException { - mHandler.post(() -> mHealthCheckResultCallback = callback::sendResult); - } - - @Override - public void request(String packageName) throws RemoteException { - mHandler.post(() -> ExplicitHealthCheckService.this.onRequestHealthCheck(packageName)); - } - - @Override - public void cancel(String packageName) throws RemoteException { - mHandler.post(() -> ExplicitHealthCheckService.this.onCancelHealthCheck(packageName)); - } - - @Override - public void getSupportedPackages(RemoteCallback callback) throws RemoteException { - mHandler.post(() -> { - List<PackageConfig> packages = - ExplicitHealthCheckService.this.onGetSupportedPackages(); - Objects.requireNonNull(packages, "Supported package list must be non-null"); - Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(EXTRA_SUPPORTED_PACKAGES, new ArrayList<>(packages)); - callback.sendResult(bundle); - }); - } - - @Override - public void getRequestedPackages(RemoteCallback callback) throws RemoteException { - mHandler.post(() -> { - List<String> packages = - ExplicitHealthCheckService.this.onGetRequestedPackages(); - Objects.requireNonNull(packages, "Requested package list must be non-null"); - Bundle bundle = new Bundle(); - bundle.putStringArrayList(EXTRA_REQUESTED_PACKAGES, new ArrayList<>(packages)); - callback.sendResult(bundle); - }); - } - } -} diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS b/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS deleted file mode 100644 index 1c045e10c0ec..000000000000 --- a/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -narayan@google.com -nandana@google.com -olilan@google.com diff --git a/packages/CrashRecovery/services/module/java/com/android/server/ExplicitHealthCheckController.java b/packages/CrashRecovery/services/module/java/com/android/server/ExplicitHealthCheckController.java deleted file mode 100644 index da9a13961f79..000000000000 --- a/packages/CrashRecovery/services/module/java/com/android/server/ExplicitHealthCheckController.java +++ /dev/null @@ -1,447 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE; -import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_REQUESTED_PACKAGES; -import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_SUPPORTED_PACKAGES; -import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig; - -import android.Manifest; -import android.annotation.MainThread; -import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.os.IBinder; -import android.os.RemoteCallback; -import android.os.RemoteException; -import android.os.UserHandle; -import android.service.watchdog.ExplicitHealthCheckService; -import android.service.watchdog.IExplicitHealthCheckService; -import android.text.TextUtils; -import android.util.ArraySet; -import android.util.Slog; - -import com.android.internal.annotations.GuardedBy; - -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.function.Consumer; - -// TODO(b/120598832): Add tests -/** - * Controls the connections with {@link ExplicitHealthCheckService}. - */ -class ExplicitHealthCheckController { - private static final String TAG = "ExplicitHealthCheckController"; - private final Object mLock = new Object(); - private final Context mContext; - - // Called everytime a package passes the health check, so the watchdog is notified of the - // passing check. In practice, should never be null after it has been #setEnabled. - // To prevent deadlocks between the controller and watchdog threads, we have - // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class. - // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer. - @GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer; - // Called everytime after a successful #syncRequest call, so the watchdog can receive packages - // supporting health checks and update its internal state. In practice, should never be null - // after it has been #setEnabled. - // To prevent deadlocks between the controller and watchdog threads, we have - // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class. - // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer. - @GuardedBy("mLock") @Nullable private Consumer<List<PackageConfig>> mSupportedConsumer; - // Called everytime we need to notify the watchdog to sync requests between itself and the - // health check service. In practice, should never be null after it has been #setEnabled. - // To prevent deadlocks between the controller and watchdog threads, we have - // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class. - // It's easier to just NOT hold #mLock when calling into watchdog code on this runnable. - @GuardedBy("mLock") @Nullable private Runnable mNotifySyncRunnable; - // Actual binder object to the explicit health check service. - @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService; - // Connection to the explicit health check service, necessary to unbind. - // We should only try to bind if mConnection is null, non-null indicates we - // are connected or at least connecting. - @GuardedBy("mLock") @Nullable private ServiceConnection mConnection; - // Bind state of the explicit health check service. - @GuardedBy("mLock") private boolean mEnabled; - - ExplicitHealthCheckController(Context context) { - mContext = context; - } - - /** Enables or disables explicit health checks. */ - public void setEnabled(boolean enabled) { - synchronized (mLock) { - Slog.i(TAG, "Explicit health checks " + (enabled ? "enabled." : "disabled.")); - mEnabled = enabled; - } - } - - /** - * Sets callbacks to listen to important events from the controller. - * - * <p> Should be called once at initialization before any other calls to the controller to - * ensure a happens-before relationship of the set parameters and visibility on other threads. - */ - public void setCallbacks(Consumer<String> passedConsumer, - Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable) { - synchronized (mLock) { - if (mPassedConsumer != null || mSupportedConsumer != null - || mNotifySyncRunnable != null) { - Slog.wtf(TAG, "Resetting health check controller callbacks"); - } - - mPassedConsumer = Objects.requireNonNull(passedConsumer); - mSupportedConsumer = Objects.requireNonNull(supportedConsumer); - mNotifySyncRunnable = Objects.requireNonNull(notifySyncRunnable); - } - } - - /** - * Calls the health check service to request or cancel packages based on - * {@code newRequestedPackages}. - * - * <p> Supported packages in {@code newRequestedPackages} that have not been previously - * requested will be requested while supported packages not in {@code newRequestedPackages} - * but were previously requested will be cancelled. - * - * <p> This handles binding and unbinding to the health check service as required. - * - * <p> Note, calling this may modify {@code newRequestedPackages}. - * - * <p> Note, this method is not thread safe, all calls should be serialized. - */ - public void syncRequests(Set<String> newRequestedPackages) { - boolean enabled; - synchronized (mLock) { - enabled = mEnabled; - } - - if (!enabled) { - Slog.i(TAG, "Health checks disabled, no supported packages"); - // Call outside lock - mSupportedConsumer.accept(Collections.emptyList()); - return; - } - - getSupportedPackages(supportedPackageConfigs -> { - // Notify the watchdog without lock held - mSupportedConsumer.accept(supportedPackageConfigs); - getRequestedPackages(previousRequestedPackages -> { - synchronized (mLock) { - // Hold lock so requests and cancellations are sent atomically. - // It is important we don't mix requests from multiple threads. - - Set<String> supportedPackages = new ArraySet<>(); - for (PackageConfig config : supportedPackageConfigs) { - supportedPackages.add(config.getPackageName()); - } - // Note, this may modify newRequestedPackages - newRequestedPackages.retainAll(supportedPackages); - - // Cancel packages no longer requested - actOnDifference(previousRequestedPackages, - newRequestedPackages, p -> cancel(p)); - // Request packages not yet requested - actOnDifference(newRequestedPackages, - previousRequestedPackages, p -> request(p)); - - if (newRequestedPackages.isEmpty()) { - Slog.i(TAG, "No more health check requests, unbinding..."); - unbindService(); - return; - } - } - }); - }); - } - - private void actOnDifference(Collection<String> collection1, Collection<String> collection2, - Consumer<String> action) { - Iterator<String> iterator = collection1.iterator(); - while (iterator.hasNext()) { - String packageName = iterator.next(); - if (!collection2.contains(packageName)) { - action.accept(packageName); - } - } - } - - /** - * Requests an explicit health check for {@code packageName}. - * After this request, the callback registered on {@link #setCallbacks} can receive explicit - * health check passed results. - */ - private void request(String packageName) { - synchronized (mLock) { - if (!prepareServiceLocked("request health check for " + packageName)) { - return; - } - - Slog.i(TAG, "Requesting health check for package " + packageName); - try { - mRemoteService.request(packageName); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to request health check for package " + packageName, e); - } - } - } - - /** - * Cancels all explicit health checks for {@code packageName}. - * After this request, the callback registered on {@link #setCallbacks} can no longer receive - * explicit health check passed results. - */ - private void cancel(String packageName) { - synchronized (mLock) { - if (!prepareServiceLocked("cancel health check for " + packageName)) { - return; - } - - Slog.i(TAG, "Cancelling health check for package " + packageName); - try { - mRemoteService.cancel(packageName); - } catch (RemoteException e) { - // Do nothing, if the service is down, when it comes up, we will sync requests, - // if there's some other error, retrying wouldn't fix anyways. - Slog.w(TAG, "Failed to cancel health check for package " + packageName, e); - } - } - } - - /** - * Returns the packages that we can request explicit health checks for. - * The packages will be returned to the {@code consumer}. - */ - private void getSupportedPackages(Consumer<List<PackageConfig>> consumer) { - synchronized (mLock) { - if (!prepareServiceLocked("get health check supported packages")) { - return; - } - - Slog.d(TAG, "Getting health check supported packages"); - try { - mRemoteService.getSupportedPackages(new RemoteCallback(result -> { - List<PackageConfig> packages = - result.getParcelableArrayList(EXTRA_SUPPORTED_PACKAGES, android.service.watchdog.ExplicitHealthCheckService.PackageConfig.class); - Slog.i(TAG, "Explicit health check supported packages " + packages); - consumer.accept(packages); - })); - } catch (RemoteException e) { - // Request failed, treat as if all observed packages are supported, if any packages - // expire during this period, we may incorrectly treat it as failing health checks - // even if we don't support health checks for the package. - Slog.w(TAG, "Failed to get health check supported packages", e); - } - } - } - - /** - * Returns the packages for which health checks are currently in progress. - * The packages will be returned to the {@code consumer}. - */ - private void getRequestedPackages(Consumer<List<String>> consumer) { - synchronized (mLock) { - if (!prepareServiceLocked("get health check requested packages")) { - return; - } - - Slog.d(TAG, "Getting health check requested packages"); - try { - mRemoteService.getRequestedPackages(new RemoteCallback(result -> { - List<String> packages = result.getStringArrayList(EXTRA_REQUESTED_PACKAGES); - Slog.i(TAG, "Explicit health check requested packages " + packages); - consumer.accept(packages); - })); - } catch (RemoteException e) { - // Request failed, treat as if we haven't requested any packages, if any packages - // were actually requested, they will not be cancelled now. May be cancelled later - Slog.w(TAG, "Failed to get health check requested packages", e); - } - } - } - - /** - * Binds to the explicit health check service if the controller is enabled and - * not already bound. - */ - private void bindService() { - synchronized (mLock) { - if (!mEnabled || mConnection != null || mRemoteService != null) { - if (!mEnabled) { - Slog.i(TAG, "Not binding to service, service disabled"); - } else if (mRemoteService != null) { - Slog.i(TAG, "Not binding to service, service already connected"); - } else { - Slog.i(TAG, "Not binding to service, service already connecting"); - } - return; - } - ComponentName component = getServiceComponentNameLocked(); - if (component == null) { - Slog.wtf(TAG, "Explicit health check service not found"); - return; - } - - Intent intent = new Intent(); - intent.setComponent(component); - mConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - Slog.i(TAG, "Explicit health check service is connected " + name); - initState(service); - } - - @Override - @MainThread - public void onServiceDisconnected(ComponentName name) { - // Service crashed or process was killed, #onServiceConnected will be called. - // Don't need to re-bind. - Slog.i(TAG, "Explicit health check service is disconnected " + name); - synchronized (mLock) { - mRemoteService = null; - } - } - - @Override - public void onBindingDied(ComponentName name) { - // Application hosting service probably got updated - // Need to re-bind. - Slog.i(TAG, "Explicit health check service binding is dead. Rebind: " + name); - unbindService(); - bindService(); - } - - @Override - public void onNullBinding(ComponentName name) { - // Should never happen. Service returned null from #onBind. - Slog.wtf(TAG, "Explicit health check service binding is null?? " + name); - } - }; - - mContext.bindServiceAsUser(intent, mConnection, - Context.BIND_AUTO_CREATE, UserHandle.SYSTEM); - Slog.i(TAG, "Explicit health check service is bound"); - } - } - - /** Unbinds the explicit health check service. */ - private void unbindService() { - synchronized (mLock) { - if (mRemoteService != null) { - mContext.unbindService(mConnection); - mRemoteService = null; - mConnection = null; - } - Slog.i(TAG, "Explicit health check service is unbound"); - } - } - - @GuardedBy("mLock") - @Nullable - private ServiceInfo getServiceInfoLocked() { - final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE); - final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, - PackageManager.GET_SERVICES | PackageManager.GET_META_DATA - | PackageManager.MATCH_SYSTEM_ONLY); - if (resolveInfo == null || resolveInfo.serviceInfo == null) { - Slog.w(TAG, "No valid components found."); - return null; - } - return resolveInfo.serviceInfo; - } - - @GuardedBy("mLock") - @Nullable - private ComponentName getServiceComponentNameLocked() { - final ServiceInfo serviceInfo = getServiceInfoLocked(); - if (serviceInfo == null) { - return null; - } - - final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name); - if (!Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE - .equals(serviceInfo.permission)) { - Slog.w(TAG, name.flattenToShortString() + " does not require permission " - + Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE); - return null; - } - return name; - } - - private void initState(IBinder service) { - synchronized (mLock) { - if (!mEnabled) { - Slog.w(TAG, "Attempting to connect disabled service?? Unbinding..."); - // Very unlikely, but we disabled the service after binding but before we connected - unbindService(); - return; - } - mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service); - try { - mRemoteService.setCallback(new RemoteCallback(result -> { - String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE); - if (!TextUtils.isEmpty(packageName)) { - if (mPassedConsumer == null) { - Slog.wtf(TAG, "Health check passed for package " + packageName - + "but no consumer registered."); - } else { - // Call without lock held - mPassedConsumer.accept(packageName); - } - } else { - Slog.wtf(TAG, "Empty package passed explicit health check?"); - } - })); - Slog.i(TAG, "Service initialized, syncing requests"); - } catch (RemoteException e) { - Slog.wtf(TAG, "Could not setCallback on explicit health check service"); - } - } - // Calling outside lock - mNotifySyncRunnable.run(); - } - - /** - * Prepares the health check service to receive requests. - * - * @return {@code true} if it is ready and we can proceed with a request, - * {@code false} otherwise. If it is not ready, and the service is enabled, - * we will bind and the request should be automatically attempted later. - */ - @GuardedBy("mLock") - private boolean prepareServiceLocked(String action) { - if (mRemoteService != null && mEnabled) { - return true; - } - Slog.i(TAG, "Service not ready to " + action - + (mEnabled ? ". Binding..." : ". Disabled")); - if (mEnabled) { - bindService(); - } - return false; - } -} diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java deleted file mode 100644 index e4f07f9fc213..000000000000 --- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java +++ /dev/null @@ -1,2253 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import static android.content.Intent.ACTION_REBOOT; -import static android.content.Intent.ACTION_SHUTDOWN; -import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig; -import static android.util.Xml.Encoding.UTF_8; - -import static com.android.server.crashrecovery.CrashRecoveryUtils.dumpCrashRecoveryEvents; - -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.annotation.CallbackExecutor; -import android.annotation.FlaggedApi; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.VersionedPackage; -import android.crashrecovery.flags.Flags; -import android.os.Environment; -import android.os.Handler; -import android.os.Looper; -import android.os.Process; -import android.os.SystemProperties; -import android.provider.DeviceConfig; -import android.sysprop.CrashRecoveryProperties; -import android.text.TextUtils; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.AtomicFile; -import android.util.EventLog; -import android.util.IndentingPrintWriter; -import android.util.LongArrayQueue; -import android.util.Slog; -import android.util.Xml; -import android.util.XmlUtils; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.FastXmlSerializer; -import com.android.modules.utils.BackgroundThread; - -import libcore.io.IoUtils; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; - -/** - * Monitors the health of packages on the system and notifies interested observers when packages - * fail. On failure, the registered observer with the least user impacting mitigation will - * be notified. - * @hide - */ -@FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY) -@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) -public class PackageWatchdog { - private static final String TAG = "PackageWatchdog"; - - static final String PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS = - "watchdog_trigger_failure_duration_millis"; - static final String PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT = - "watchdog_trigger_failure_count"; - static final String PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED = - "watchdog_explicit_health_check_enabled"; - - // TODO: make the following values configurable via DeviceConfig - private static final long NATIVE_CRASH_POLLING_INTERVAL_MILLIS = - TimeUnit.SECONDS.toMillis(30); - private static final long NUMBER_OF_NATIVE_CRASH_POLLS = 10; - - - /** Reason for package failure could not be determined. */ - public static final int FAILURE_REASON_UNKNOWN = 0; - - /** The package had a native crash. */ - public static final int FAILURE_REASON_NATIVE_CRASH = 1; - - /** The package failed an explicit health check. */ - public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2; - - /** The app crashed. */ - public static final int FAILURE_REASON_APP_CRASH = 3; - - /** The app was not responding. */ - public static final int FAILURE_REASON_APP_NOT_RESPONDING = 4; - - /** The device was boot looping. */ - public static final int FAILURE_REASON_BOOT_LOOP = 5; - - /** @hide */ - @IntDef(prefix = { "FAILURE_REASON_" }, value = { - FAILURE_REASON_UNKNOWN, - FAILURE_REASON_NATIVE_CRASH, - FAILURE_REASON_EXPLICIT_HEALTH_CHECK, - FAILURE_REASON_APP_CRASH, - FAILURE_REASON_APP_NOT_RESPONDING, - FAILURE_REASON_BOOT_LOOP - }) - @Retention(RetentionPolicy.SOURCE) - public @interface FailureReasons {} - - // Duration to count package failures before it resets to 0 - @VisibleForTesting - static final int DEFAULT_TRIGGER_FAILURE_DURATION_MS = - (int) TimeUnit.MINUTES.toMillis(1); - // Number of package failures within the duration above before we notify observers - @VisibleForTesting - static final int DEFAULT_TRIGGER_FAILURE_COUNT = 5; - @VisibleForTesting - static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2); - // Sliding window for tracking how many mitigation calls were made for a package. - @VisibleForTesting - static final long DEFAULT_DEESCALATION_WINDOW_MS = TimeUnit.HOURS.toMillis(1); - // Whether explicit health checks are enabled or not - private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true; - - @VisibleForTesting - static final int DEFAULT_BOOT_LOOP_TRIGGER_COUNT = 5; - - static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10); - - // Time needed to apply mitigation - private static final String MITIGATION_WINDOW_MS = - "persist.device_config.configuration.mitigation_window_ms"; - @VisibleForTesting - static final long DEFAULT_MITIGATION_WINDOW_MS = TimeUnit.SECONDS.toMillis(5); - - // Threshold level at which or above user might experience significant disruption. - private static final String MAJOR_USER_IMPACT_LEVEL_THRESHOLD = - "persist.device_config.configuration.major_user_impact_level_threshold"; - private static final int DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD = - PackageHealthObserverImpact.USER_IMPACT_LEVEL_71; - - // Comma separated list of all packages exempt from user impact level threshold. If a package - // in the list is crash looping, all the mitigations including factory reset will be performed. - private static final String PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD = - "persist.device_config.configuration.packages_exempt_from_impact_level_threshold"; - - // Comma separated list of default packages exempt from user impact level threshold. - private static final String DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD = - "com.android.systemui"; - - private long mNumberOfNativeCrashPollsRemaining; - - private static final int DB_VERSION = 1; - private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog"; - private static final String TAG_PACKAGE = "package"; - private static final String TAG_OBSERVER = "observer"; - private static final String ATTR_VERSION = "version"; - private static final String ATTR_NAME = "name"; - private static final String ATTR_DURATION = "duration"; - private static final String ATTR_EXPLICIT_HEALTH_CHECK_DURATION = "health-check-duration"; - private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check"; - private static final String ATTR_MITIGATION_CALLS = "mitigation-calls"; - private static final String ATTR_MITIGATION_COUNT = "mitigation-count"; - - // A file containing information about the current mitigation count in the case of a boot loop. - // This allows boot loop information to persist in the case of an fs-checkpoint being - // aborted. - private static final String METADATA_FILE = "/metadata/watchdog/mitigation_count.txt"; - - /** - * EventLog tags used when logging into the event log. Note the values must be sync with - * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct - * name translation. - */ - private static final int LOG_TAG_RESCUE_NOTE = 2900; - - private static final Object sPackageWatchdogLock = new Object(); - @GuardedBy("sPackageWatchdogLock") - private static PackageWatchdog sPackageWatchdog; - - private static final Object sLock = new Object(); - // System server context - private final Context mContext; - // Handler to run short running tasks - private final Handler mShortTaskHandler; - // Handler for processing IO and long running tasks - private final Handler mLongTaskHandler; - // Contains (observer-name -> observer-handle) that have ever been registered from - // previous boots. Observers with all packages expired are periodically pruned. - // It is saved to disk on system shutdown and repouplated on startup so it survives reboots. - @GuardedBy("sLock") - private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>(); - // File containing the XML data of monitored packages /data/system/package-watchdog.xml - private final AtomicFile mPolicyFile; - private final ExplicitHealthCheckController mHealthCheckController; - private final Runnable mSyncRequests = this::syncRequests; - private final Runnable mSyncStateWithScheduledReason = this::syncStateWithScheduledReason; - private final Runnable mSaveToFile = this::saveToFile; - private final SystemClock mSystemClock; - private final BootThreshold mBootThreshold; - private final DeviceConfig.OnPropertiesChangedListener - mOnPropertyChangedListener = this::onPropertyChanged; - - private final Set<String> mPackagesExemptFromImpactLevelThreshold = new ArraySet<>(); - - // The set of packages that have been synced with the ExplicitHealthCheckController - @GuardedBy("sLock") - private Set<String> mRequestedHealthCheckPackages = new ArraySet<>(); - @GuardedBy("sLock") - private boolean mIsPackagesReady; - // Flag to control whether explicit health checks are supported or not - @GuardedBy("sLock") - private boolean mIsHealthCheckEnabled = DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED; - @GuardedBy("sLock") - private int mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS; - @GuardedBy("sLock") - private int mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT; - // SystemClock#uptimeMillis when we last executed #syncState - // 0 if no prune is scheduled. - @GuardedBy("sLock") - private long mUptimeAtLastStateSync; - // If true, sync explicit health check packages with the ExplicitHealthCheckController. - @GuardedBy("sLock") - private boolean mSyncRequired = false; - - @GuardedBy("sLock") - private long mLastMitigation = -1000000; - - @FunctionalInterface - @VisibleForTesting - interface SystemClock { - long uptimeMillis(); - } - - private PackageWatchdog(Context context) { - // Needs to be constructed inline - this(context, new AtomicFile( - new File(new File(Environment.getDataDirectory(), "system"), - "package-watchdog.xml")), - new Handler(Looper.myLooper()), BackgroundThread.getHandler(), - new ExplicitHealthCheckController(context), - android.os.SystemClock::uptimeMillis); - } - - /** - * Creates a PackageWatchdog that allows injecting dependencies. - */ - @VisibleForTesting - PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler, - Handler longTaskHandler, ExplicitHealthCheckController controller, - SystemClock clock) { - mContext = context; - mPolicyFile = policyFile; - mShortTaskHandler = shortTaskHandler; - mLongTaskHandler = longTaskHandler; - mHealthCheckController = controller; - mSystemClock = clock; - mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS; - mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT, - DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS); - - loadFromFile(); - sPackageWatchdog = this; - } - - /** - * Creates or gets singleton instance of PackageWatchdog. - * - * @param context The system server context. - */ - public static @NonNull PackageWatchdog getInstance(@NonNull Context context) { - synchronized (sPackageWatchdogLock) { - if (sPackageWatchdog == null) { - new PackageWatchdog(context); - } - return sPackageWatchdog; - } - } - - /** - * Called during boot to notify when packages are ready on the device so we can start - * binding. - * @hide - */ - public void onPackagesReady() { - synchronized (sLock) { - mIsPackagesReady = true; - mHealthCheckController.setCallbacks(packageName -> onHealthCheckPassed(packageName), - packages -> onSupportedPackages(packages), - this::onSyncRequestNotified); - setPropertyChangedListenerLocked(); - updateConfigs(); - } - } - - /** - * Registers {@code observer} to listen for package failures. Add a new ObserverInternal for - * this observer if it does not already exist. - * For executing mitigations observers will receive callback on the given executor. - * - * <p>Observers are expected to call this on boot. It does not specify any packages but - * it will resume observing any packages requested from a previous boot. - * - * @param observer instance of {@link PackageHealthObserver} for observing package failures - * and boot loops. - * @param executor Executor for the thread on which observers would receive callbacks - */ - public void registerHealthObserver(@NonNull @CallbackExecutor Executor executor, - @NonNull PackageHealthObserver observer) { - synchronized (sLock) { - ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier()); - if (internalObserver != null) { - internalObserver.registeredObserver = observer; - internalObserver.observerExecutor = executor; - } else { - internalObserver = new ObserverInternal(observer.getUniqueIdentifier(), - new ArrayList<>()); - internalObserver.registeredObserver = observer; - internalObserver.observerExecutor = executor; - mAllObservers.put(observer.getUniqueIdentifier(), internalObserver); - syncState("added new observer"); - } - } - } - - /** - * Starts observing the health of the {@code packages} for {@code observer}. - * Note: Observer needs to be registered with {@link #registerHealthObserver} before calling - * this API. - * - * <p>If monitoring a package supporting explicit health check, at the end of the monitoring - * duration if {@link #onHealthCheckPassed} was never called, - * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} will be called as if the - * package failed. - * - * <p>If {@code observer} is already monitoring a package in {@code packageNames}, - * the monitoring window of that package will be reset to {@code durationMs} and the health - * check state will be reset to a default. - * - * <p>The {@code observer} must be registered with {@link #registerHealthObserver} before - * calling this method. - * - * @param packageNames The list of packages to check. If this is empty, the call will be a - * no-op. - * - * @param timeoutMs The timeout after which Explicit Health Checks would not run. If this is - * less than 1, a default monitoring duration 2 days will be used. - * - * @throws IllegalStateException if the observer was not previously registered - */ - public void startExplicitHealthCheck(@NonNull List<String> packageNames, long timeoutMs, - @NonNull PackageHealthObserver observer) { - synchronized (sLock) { - if (!mAllObservers.containsKey(observer.getUniqueIdentifier())) { - Slog.wtf(TAG, "No observer found, need to register the observer: " - + observer.getUniqueIdentifier()); - throw new IllegalStateException("Observer not registered"); - } - } - if (packageNames.isEmpty()) { - Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier()); - return; - } - if (timeoutMs < 1) { - Slog.wtf(TAG, "Invalid duration " + timeoutMs + "ms for observer " - + observer.getUniqueIdentifier() + ". Not observing packages " + packageNames); - timeoutMs = DEFAULT_OBSERVING_DURATION_MS; - } - - List<MonitoredPackage> packages = new ArrayList<>(); - for (int i = 0; i < packageNames.size(); i++) { - // Health checks not available yet so health check state will start INACTIVE - MonitoredPackage pkg = newMonitoredPackage(packageNames.get(i), timeoutMs, false); - if (pkg != null) { - packages.add(pkg); - } else { - Slog.w(TAG, "Failed to create MonitoredPackage for pkg=" + packageNames.get(i)); - } - } - - if (packages.isEmpty()) { - return; - } - - // Sync before we add the new packages to the observers. This will #pruneObservers, - // causing any elapsed time to be deducted from all existing packages before we add new - // packages. This maintains the invariant that the elapsed time for ALL (new and existing) - // packages is the same. - mLongTaskHandler.post(() -> { - syncState("observing new packages"); - - synchronized (sLock) { - ObserverInternal oldObserver = mAllObservers.get(observer.getUniqueIdentifier()); - if (oldObserver == null) { - Slog.d(TAG, observer.getUniqueIdentifier() + " started monitoring health " - + "of packages " + packageNames); - mAllObservers.put(observer.getUniqueIdentifier(), - new ObserverInternal(observer.getUniqueIdentifier(), packages)); - } else { - Slog.d(TAG, observer.getUniqueIdentifier() + " added the following " - + "packages to monitor " + packageNames); - oldObserver.updatePackagesLocked(packages); - } - } - - // Sync after we add the new packages to the observers. We may have received packges - // requiring an earlier schedule than we are currently scheduled for. - syncState("updated observers"); - }); - - } - - /** - * Unregisters {@code observer} from listening to package failure. - * Additionally, this stops observing any packages that may have previously been observed - * even from a previous boot. - */ - public void unregisterHealthObserver(@NonNull PackageHealthObserver observer) { - mLongTaskHandler.post(() -> { - synchronized (sLock) { - mAllObservers.remove(observer.getUniqueIdentifier()); - } - syncState("unregistering observer: " + observer.getUniqueIdentifier()); - }); - } - - /** - * Called when a process fails due to a crash, ANR or explicit health check. - * - * <p>For each package contained in the process, one registered observer with the least user - * impact will be notified for mitigation. - * - * <p>This method could be called frequently if there is a severe problem on the device. - */ - public void notifyPackageFailure(@NonNull List<VersionedPackage> packages, - @FailureReasons int failureReason) { - if (packages == null) { - Slog.w(TAG, "Could not resolve a list of failing packages"); - return; - } - synchronized (sLock) { - final long now = mSystemClock.uptimeMillis(); - if (Flags.recoverabilityDetection()) { - if (now >= mLastMitigation - && (now - mLastMitigation) < getMitigationWindowMs()) { - Slog.i(TAG, "Skipping notifyPackageFailure mitigation"); - return; - } - } - } - mLongTaskHandler.post(() -> { - synchronized (sLock) { - if (mAllObservers.isEmpty()) { - return; - } - boolean requiresImmediateAction = (failureReason == FAILURE_REASON_NATIVE_CRASH - || failureReason == FAILURE_REASON_EXPLICIT_HEALTH_CHECK); - if (requiresImmediateAction) { - handleFailureImmediately(packages, failureReason); - } else { - for (int pIndex = 0; pIndex < packages.size(); pIndex++) { - VersionedPackage versionedPackage = packages.get(pIndex); - // Observer that will receive failure for versionedPackage - ObserverInternal currentObserverToNotify = null; - int currentObserverImpact = Integer.MAX_VALUE; - MonitoredPackage currentMonitoredPackage = null; - - // Find observer with least user impact - for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) { - ObserverInternal observer = mAllObservers.valueAt(oIndex); - PackageHealthObserver registeredObserver = observer.registeredObserver; - if (registeredObserver != null - && observer.notifyPackageFailureLocked( - versionedPackage.getPackageName())) { - MonitoredPackage p = observer.getMonitoredPackage( - versionedPackage.getPackageName()); - int mitigationCount = 1; - if (p != null) { - mitigationCount = p.getMitigationCountLocked() + 1; - } - int impact = registeredObserver.onHealthCheckFailed( - versionedPackage, failureReason, mitigationCount); - if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0 - && impact < currentObserverImpact) { - currentObserverToNotify = observer; - currentObserverImpact = impact; - currentMonitoredPackage = p; - } - } - } - - // Execute action with least user impact - if (currentObserverToNotify != null) { - int mitigationCount; - if (currentMonitoredPackage != null) { - currentMonitoredPackage.noteMitigationCallLocked(); - mitigationCount = - currentMonitoredPackage.getMitigationCountLocked(); - } else { - mitigationCount = 1; - } - if (Flags.recoverabilityDetection()) { - maybeExecute(currentObserverToNotify, versionedPackage, - failureReason, currentObserverImpact, mitigationCount); - } else { - PackageHealthObserver registeredObserver = - currentObserverToNotify.registeredObserver; - currentObserverToNotify.observerExecutor.execute(() -> - registeredObserver.onExecuteHealthCheckMitigation( - versionedPackage, failureReason, mitigationCount)); - } - } - } - } - } - }); - } - - /** - * For native crashes or explicit health check failures, call directly into each observer to - * mitigate the error without going through failure threshold logic. - */ - @GuardedBy("sLock") - private void handleFailureImmediately(List<VersionedPackage> packages, - @FailureReasons int failureReason) { - VersionedPackage failingPackage = packages.size() > 0 ? packages.get(0) : null; - ObserverInternal currentObserverToNotify = null; - int currentObserverImpact = Integer.MAX_VALUE; - for (ObserverInternal observer: mAllObservers.values()) { - PackageHealthObserver registeredObserver = observer.registeredObserver; - if (registeredObserver != null) { - int impact = registeredObserver.onHealthCheckFailed( - failingPackage, failureReason, 1); - if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0 - && impact < currentObserverImpact) { - currentObserverToNotify = observer; - currentObserverImpact = impact; - } - } - } - if (currentObserverToNotify != null) { - if (Flags.recoverabilityDetection()) { - maybeExecute(currentObserverToNotify, failingPackage, failureReason, - currentObserverImpact, /*mitigationCount=*/ 1); - } else { - PackageHealthObserver registeredObserver = - currentObserverToNotify.registeredObserver; - currentObserverToNotify.observerExecutor.execute(() -> - registeredObserver.onExecuteHealthCheckMitigation(failingPackage, - failureReason, 1)); - - } - } - } - - private void maybeExecute(ObserverInternal currentObserverToNotify, - VersionedPackage versionedPackage, - @FailureReasons int failureReason, - int currentObserverImpact, - int mitigationCount) { - if (allowMitigations(currentObserverImpact, versionedPackage)) { - PackageHealthObserver registeredObserver; - synchronized (sLock) { - mLastMitigation = mSystemClock.uptimeMillis(); - registeredObserver = currentObserverToNotify.registeredObserver; - } - currentObserverToNotify.observerExecutor.execute(() -> - registeredObserver.onExecuteHealthCheckMitigation(versionedPackage, - failureReason, mitigationCount)); - } - } - - private boolean allowMitigations(int currentObserverImpact, - VersionedPackage versionedPackage) { - return currentObserverImpact < getUserImpactLevelLimit() - || getPackagesExemptFromImpactLevelThreshold().contains( - versionedPackage.getPackageName()); - } - - private long getMitigationWindowMs() { - return SystemProperties.getLong(MITIGATION_WINDOW_MS, DEFAULT_MITIGATION_WINDOW_MS); - } - - - /** - * Called when the system server boots. If the system server is detected to be in a boot loop, - * query each observer and perform the mitigation action with the lowest user impact. - * - * Note: PackageWatchdog considers system_server restart loop as bootloop. Full reboots - * are not counted in bootloop. - * @hide - */ - @SuppressWarnings("GuardedBy") - public void noteBoot() { - synchronized (sLock) { - // if boot count has reached threshold, start mitigation. - // We wait until threshold number of restarts only for the first time. Perform - // mitigations for every restart after that. - boolean mitigate = mBootThreshold.incrementAndTest(); - if (mitigate) { - if (!Flags.recoverabilityDetection()) { - mBootThreshold.reset(); - } - int mitigationCount = mBootThreshold.getMitigationCount() + 1; - ObserverInternal currentObserverToNotify = null; - int currentObserverImpact = Integer.MAX_VALUE; - for (int i = 0; i < mAllObservers.size(); i++) { - final ObserverInternal observer = mAllObservers.valueAt(i); - PackageHealthObserver registeredObserver = observer.registeredObserver; - if (registeredObserver != null) { - int impact = Flags.recoverabilityDetection() - ? registeredObserver.onBootLoop( - observer.getBootMitigationCount() + 1) - : registeredObserver.onBootLoop(mitigationCount); - if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0 - && impact < currentObserverImpact) { - currentObserverToNotify = observer; - currentObserverImpact = impact; - } - } - } - - if (currentObserverToNotify != null) { - PackageHealthObserver registeredObserver = - currentObserverToNotify.registeredObserver; - if (Flags.recoverabilityDetection()) { - int currentObserverMitigationCount = - currentObserverToNotify.getBootMitigationCount() + 1; - currentObserverToNotify.setBootMitigationCount( - currentObserverMitigationCount); - saveAllObserversBootMitigationCountToMetadata(METADATA_FILE); - currentObserverToNotify.observerExecutor - .execute(() -> registeredObserver.onExecuteBootLoopMitigation( - currentObserverMitigationCount)); - } else { - mBootThreshold.setMitigationCount(mitigationCount); - mBootThreshold.saveMitigationCountToMetadata(); - currentObserverToNotify.observerExecutor - .execute(() -> registeredObserver.onExecuteBootLoopMitigation( - mitigationCount)); - - } - } - } - } - } - - // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also - // avoid holding lock? - // This currently adds about 7ms extra to shutdown thread - /** @hide Writes the package information to file during shutdown. */ - public void writeNow() { - synchronized (sLock) { - // Must only run synchronous tasks as this runs on the ShutdownThread and no other - // thread is guaranteed to run during shutdown. - if (!mAllObservers.isEmpty()) { - mLongTaskHandler.removeCallbacks(mSaveToFile); - pruneObserversLocked(); - saveToFile(); - Slog.i(TAG, "Last write to update package durations"); - } - } - } - - /** - * Enables or disables explicit health checks. - * <p> If explicit health checks are enabled, the health check service is started. - * <p> If explicit health checks are disabled, pending explicit health check requests are - * passed and the health check service is stopped. - */ - private void setExplicitHealthCheckEnabled(boolean enabled) { - synchronized (sLock) { - mIsHealthCheckEnabled = enabled; - mHealthCheckController.setEnabled(enabled); - mSyncRequired = true; - // Prune to update internal state whenever health check is enabled/disabled - syncState("health check state " + (enabled ? "enabled" : "disabled")); - } - } - - /** - * This method should be only called on mShortTaskHandler, since it modifies - * {@link #mNumberOfNativeCrashPollsRemaining}. - */ - private void checkAndMitigateNativeCrashes() { - mNumberOfNativeCrashPollsRemaining--; - // Check if native watchdog reported a crash - if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) { - // We rollback all available low impact rollbacks when crash is unattributable - notifyPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH); - // we stop polling after an attempt to execute rollback, regardless of whether the - // attempt succeeds or not - } else { - if (mNumberOfNativeCrashPollsRemaining > 0) { - mShortTaskHandler.postDelayed(() -> checkAndMitigateNativeCrashes(), - NATIVE_CRASH_POLLING_INTERVAL_MILLIS); - } - } - } - - /** - * Since this method can eventually trigger a rollback, it should be called - * only once boot has completed {@code onBootCompleted} and not earlier, because the install - * session must be entirely completed before we try to rollback. - * @hide - */ - public void scheduleCheckAndMitigateNativeCrashes() { - Slog.i(TAG, "Scheduling " + mNumberOfNativeCrashPollsRemaining + " polls to check " - + "and mitigate native crashes"); - mShortTaskHandler.post(()->checkAndMitigateNativeCrashes()); - } - - private int getUserImpactLevelLimit() { - return SystemProperties.getInt(MAJOR_USER_IMPACT_LEVEL_THRESHOLD, - DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD); - } - - private Set<String> getPackagesExemptFromImpactLevelThreshold() { - if (mPackagesExemptFromImpactLevelThreshold.isEmpty()) { - String packageNames = SystemProperties.get(PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD, - DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD); - return Set.of(packageNames.split("\\s*,\\s*")); - } - return mPackagesExemptFromImpactLevelThreshold; - } - - /** - * Indicates that a mitigation was successfully triggered or executed during - * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or - * {@link PackageHealthObserver#onExecuteBootLoopMitigation}. - */ - public static final int MITIGATION_RESULT_SUCCESS = - ObserverMitigationResult.MITIGATION_RESULT_SUCCESS; - - /** - * Indicates that a mitigation executed during - * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or - * {@link PackageHealthObserver#onExecuteBootLoopMitigation} was skipped. - */ - public static final int MITIGATION_RESULT_SKIPPED = - ObserverMitigationResult.MITIGATION_RESULT_SKIPPED; - - - /** - * Possible return values of the for mitigations executed during - * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} and - * {@link PackageHealthObserver#onExecuteBootLoopMitigation}. - * @hide - */ - @Retention(SOURCE) - @IntDef(prefix = "MITIGATION_RESULT_", value = { - ObserverMitigationResult.MITIGATION_RESULT_SUCCESS, - ObserverMitigationResult.MITIGATION_RESULT_SKIPPED, - }) - public @interface ObserverMitigationResult { - int MITIGATION_RESULT_SUCCESS = 1; - int MITIGATION_RESULT_SKIPPED = 2; - } - - /** - * The minimum value that can be returned by any observer. - * It represents that no mitigations were available. - */ - public static final int USER_IMPACT_THRESHOLD_NONE = - PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; - - /** - * The mitigation impact beyond which the user will start noticing the mitigations. - */ - public static final int USER_IMPACT_THRESHOLD_MEDIUM = - PackageHealthObserverImpact.USER_IMPACT_LEVEL_20; - - /** - * The mitigation impact beyond which the user impact is severely high. - */ - public static final int USER_IMPACT_THRESHOLD_HIGH = - PackageHealthObserverImpact.USER_IMPACT_LEVEL_71; - - /** - * Possible severity values of the user impact of a - * {@link PackageHealthObserver#onExecuteHealthCheckMitigation}. - * @hide - */ - @Retention(SOURCE) - @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_10, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_20, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_30, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_40, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_50, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_70, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_71, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_75, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_80, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_90, - PackageHealthObserverImpact.USER_IMPACT_LEVEL_100}) - public @interface PackageHealthObserverImpact { - /** No action to take. */ - int USER_IMPACT_LEVEL_0 = 0; - /* Action has low user impact, user of a device will barely notice. */ - int USER_IMPACT_LEVEL_10 = 10; - /* Actions having medium user impact, user of a device will likely notice. */ - int USER_IMPACT_LEVEL_20 = 20; - int USER_IMPACT_LEVEL_30 = 30; - int USER_IMPACT_LEVEL_40 = 40; - int USER_IMPACT_LEVEL_50 = 50; - int USER_IMPACT_LEVEL_70 = 70; - /* Action has high user impact, a last resort, user of a device will be very frustrated. */ - int USER_IMPACT_LEVEL_71 = 71; - int USER_IMPACT_LEVEL_75 = 75; - int USER_IMPACT_LEVEL_80 = 80; - int USER_IMPACT_LEVEL_90 = 90; - int USER_IMPACT_LEVEL_100 = 100; - } - - /** Register instances of this interface to receive notifications on package failure. */ - @SuppressLint({"CallbackName"}) - public interface PackageHealthObserver { - /** - * Called when health check fails for the {@code versionedPackage}. - * Note: if the returned user impact is higher than {@link #USER_IMPACT_THRESHOLD_HIGH}, - * then {@link #onExecuteHealthCheckMitigation} would be called only in severe device - * conditions like boot-loop or network failure. - * - * @param versionedPackage the package that is failing. This may be null if a native - * service is crashing. - * @param failureReason the type of failure that is occurring. - * @param mitigationCount the number of times mitigation has been called for this package - * (including this time). - * - * @return any value greater than {@link #USER_IMPACT_THRESHOLD_NONE} to express - * the impact of mitigation on the user in {@link #onExecuteHealthCheckMitigation}. - * Returning {@link #USER_IMPACT_THRESHOLD_NONE} would indicate no mitigations available. - */ - @PackageHealthObserverImpact int onHealthCheckFailed( - @Nullable VersionedPackage versionedPackage, - @FailureReasons int failureReason, - int mitigationCount); - - /** - * This would be called after {@link #onHealthCheckFailed}. - * This is called only if current observer returned least impact mitigation for failed - * health check. - * - * @param versionedPackage the package that is failing. This may be null if a native - * service is crashing. - * @param failureReason the type of failure that is occurring. - * @param mitigationCount the number of times mitigation has been called for this package - * (including this time). - * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful, - * or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped. - */ - @ObserverMitigationResult int onExecuteHealthCheckMitigation( - @Nullable VersionedPackage versionedPackage, - @FailureReasons int failureReason, int mitigationCount); - - - /** - * Called when the system server has booted several times within a window of time, defined - * by {@link #mBootThreshold} - * - * @param mitigationCount the number of times mitigation has been attempted for this - * boot loop (including this time). - * - * @return any value greater than {@link #USER_IMPACT_THRESHOLD_NONE} to express - * the impact of mitigation on the user in {@link #onExecuteBootLoopMitigation}. - * Returning {@link #USER_IMPACT_THRESHOLD_NONE} would indicate no mitigations available. - */ - default @PackageHealthObserverImpact int onBootLoop(int mitigationCount) { - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; - } - - /** - * This would be called after {@link #onBootLoop}. - * This is called only if current observer returned least impact mitigation for fixing - * boot loop. - * - * @param mitigationCount the number of times mitigation has been attempted for this - * boot loop (including this time). - * - * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful, - * or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped. - */ - default @ObserverMitigationResult int onExecuteBootLoopMitigation(int mitigationCount) { - return ObserverMitigationResult.MITIGATION_RESULT_SKIPPED; - } - - // TODO(b/120598832): Ensure uniqueness? - /** - * Identifier for the observer, should not change across device updates otherwise the - * watchdog may drop observing packages with the old name. - */ - @NonNull String getUniqueIdentifier(); - - /** - * An observer will not be pruned if this is set, even if the observer is not explicitly - * monitoring any packages. - */ - default boolean isPersistent() { - return false; - } - - /** - * Returns {@code true} if this observer wishes to observe the given package, {@code false} - * otherwise. - * Any failing package can be passed on to the observer. Currently the packages that have - * ANRs and perform {@link android.service.watchdog.ExplicitHealthCheckService} are being - * passed to observers in these API. - * - * <p> A persistent observer may choose to start observing certain failing packages, even if - * it has not explicitly asked to watch the package with {@link #startExplicitHealthCheck}. - */ - default boolean mayObservePackage(@NonNull String packageName) { - return false; - } - } - - @VisibleForTesting - long getTriggerFailureCount() { - synchronized (sLock) { - return mTriggerFailureCount; - } - } - - @VisibleForTesting - long getTriggerFailureDurationMs() { - synchronized (sLock) { - return mTriggerFailureDurationMs; - } - } - - /** - * Serializes and syncs health check requests with the {@link ExplicitHealthCheckController}. - */ - private void syncRequestsAsync() { - mShortTaskHandler.removeCallbacks(mSyncRequests); - mShortTaskHandler.post(mSyncRequests); - } - - /** - * Syncs health check requests with the {@link ExplicitHealthCheckController}. - * Calls to this must be serialized. - * - * @see #syncRequestsAsync - */ - private void syncRequests() { - boolean syncRequired = false; - synchronized (sLock) { - if (mIsPackagesReady) { - Set<String> packages = getPackagesPendingHealthChecksLocked(); - if (mSyncRequired || !packages.equals(mRequestedHealthCheckPackages) - || packages.isEmpty()) { - syncRequired = true; - mRequestedHealthCheckPackages = packages; - } - } // else, we will sync requests when packages become ready - } - - // Call outside lock to avoid holding lock when calling into the controller. - if (syncRequired) { - Slog.i(TAG, "Syncing health check requests for packages: " - + mRequestedHealthCheckPackages); - mHealthCheckController.syncRequests(mRequestedHealthCheckPackages); - mSyncRequired = false; - } - } - - /** - * Updates the observers monitoring {@code packageName} that explicit health check has passed. - * - * <p> This update is strictly for registered observers at the time of the call - * Observers that register after this signal will have no knowledge of prior signals and will - * effectively behave as if the explicit health check hasn't passed for {@code packageName}. - * - * <p> {@code packageName} can still be considered failed if reported by - * {@link #notifyPackageFailureLocked} before the package expires. - * - * <p> Triggered by components outside the system server when they are fully functional after an - * update. - */ - private void onHealthCheckPassed(String packageName) { - Slog.i(TAG, "Health check passed for package: " + packageName); - boolean isStateChanged = false; - - synchronized (sLock) { - for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) { - ObserverInternal observer = mAllObservers.valueAt(observerIdx); - MonitoredPackage monitoredPackage = observer.getMonitoredPackage(packageName); - - if (monitoredPackage != null) { - int oldState = monitoredPackage.getHealthCheckStateLocked(); - int newState = monitoredPackage.tryPassHealthCheckLocked(); - isStateChanged |= oldState != newState; - } - } - } - - if (isStateChanged) { - syncState("health check passed for " + packageName); - } - } - - private void onSupportedPackages(List<PackageConfig> supportedPackages) { - boolean isStateChanged = false; - - Map<String, Long> supportedPackageTimeouts = new ArrayMap<>(); - Iterator<PackageConfig> it = supportedPackages.iterator(); - while (it.hasNext()) { - PackageConfig info = it.next(); - supportedPackageTimeouts.put(info.getPackageName(), info.getHealthCheckTimeoutMillis()); - } - - synchronized (sLock) { - Slog.d(TAG, "Received supported packages " + supportedPackages); - Iterator<ObserverInternal> oit = mAllObservers.values().iterator(); - while (oit.hasNext()) { - Iterator<MonitoredPackage> pit = oit.next().getMonitoredPackages() - .values().iterator(); - while (pit.hasNext()) { - MonitoredPackage monitoredPackage = pit.next(); - String packageName = monitoredPackage.getName(); - int oldState = monitoredPackage.getHealthCheckStateLocked(); - int newState; - - if (supportedPackageTimeouts.containsKey(packageName)) { - // Supported packages become ACTIVE if currently INACTIVE - newState = monitoredPackage.setHealthCheckActiveLocked( - supportedPackageTimeouts.get(packageName)); - } else { - // Unsupported packages are marked as PASSED unless already FAILED - newState = monitoredPackage.tryPassHealthCheckLocked(); - } - isStateChanged |= oldState != newState; - } - } - } - - if (isStateChanged) { - syncState("updated health check supported packages " + supportedPackages); - } - } - - private void onSyncRequestNotified() { - synchronized (sLock) { - mSyncRequired = true; - syncRequestsAsync(); - } - } - - @GuardedBy("sLock") - private Set<String> getPackagesPendingHealthChecksLocked() { - Set<String> packages = new ArraySet<>(); - Iterator<ObserverInternal> oit = mAllObservers.values().iterator(); - while (oit.hasNext()) { - ObserverInternal observer = oit.next(); - Iterator<MonitoredPackage> pit = - observer.getMonitoredPackages().values().iterator(); - while (pit.hasNext()) { - MonitoredPackage monitoredPackage = pit.next(); - String packageName = monitoredPackage.getName(); - if (monitoredPackage.isPendingHealthChecksLocked()) { - packages.add(packageName); - } - } - } - return packages; - } - - /** - * Syncs the state of the observers. - * - * <p> Prunes all observers, saves new state to disk, syncs health check requests with the - * health check service and schedules the next state sync. - */ - private void syncState(String reason) { - synchronized (sLock) { - Slog.i(TAG, "Syncing state, reason: " + reason); - pruneObserversLocked(); - - saveToFileAsync(); - syncRequestsAsync(); - - // Done syncing state, schedule the next state sync - scheduleNextSyncStateLocked(); - } - } - - private void syncStateWithScheduledReason() { - syncState("scheduled"); - } - - @GuardedBy("sLock") - private void scheduleNextSyncStateLocked() { - long durationMs = getNextStateSyncMillisLocked(); - mShortTaskHandler.removeCallbacks(mSyncStateWithScheduledReason); - if (durationMs == Long.MAX_VALUE) { - Slog.i(TAG, "Cancelling state sync, nothing to sync"); - mUptimeAtLastStateSync = 0; - } else { - mUptimeAtLastStateSync = mSystemClock.uptimeMillis(); - mShortTaskHandler.postDelayed(mSyncStateWithScheduledReason, durationMs); - } - } - - /** - * Returns the next duration in millis to sync the watchdog state. - * - * @returns Long#MAX_VALUE if there are no observed packages. - */ - @GuardedBy("sLock") - private long getNextStateSyncMillisLocked() { - long shortestDurationMs = Long.MAX_VALUE; - for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) { - ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex) - .getMonitoredPackages(); - for (int pIndex = 0; pIndex < packages.size(); pIndex++) { - MonitoredPackage mp = packages.valueAt(pIndex); - long duration = mp.getShortestScheduleDurationMsLocked(); - if (duration < shortestDurationMs) { - shortestDurationMs = duration; - } - } - } - return shortestDurationMs; - } - - /** - * Removes {@code elapsedMs} milliseconds from all durations on monitored packages - * and updates other internal state. - */ - @GuardedBy("sLock") - private void pruneObserversLocked() { - long elapsedMs = mUptimeAtLastStateSync == 0 - ? 0 : mSystemClock.uptimeMillis() - mUptimeAtLastStateSync; - if (elapsedMs <= 0) { - Slog.i(TAG, "Not pruning observers, elapsed time: " + elapsedMs + "ms"); - return; - } - - Iterator<ObserverInternal> it = mAllObservers.values().iterator(); - while (it.hasNext()) { - ObserverInternal observer = it.next(); - Set<MonitoredPackage> failedPackages = - observer.prunePackagesLocked(elapsedMs); - if (!failedPackages.isEmpty()) { - onHealthCheckFailed(observer, failedPackages); - } - if (observer.getMonitoredPackages().isEmpty() && (observer.registeredObserver == null - || !observer.registeredObserver.isPersistent())) { - Slog.i(TAG, "Discarding observer " + observer.name + ". All packages expired"); - it.remove(); - } - } - } - - private void onHealthCheckFailed(ObserverInternal observer, - Set<MonitoredPackage> failedPackages) { - mLongTaskHandler.post(() -> { - synchronized (sLock) { - PackageHealthObserver registeredObserver = observer.registeredObserver; - if (registeredObserver != null) { - Iterator<MonitoredPackage> it = failedPackages.iterator(); - while (it.hasNext()) { - VersionedPackage versionedPkg = getVersionedPackage(it.next().getName()); - if (versionedPkg != null) { - Slog.i(TAG, - "Explicit health check failed for package " + versionedPkg); - observer.observerExecutor.execute(() -> - registeredObserver.onExecuteHealthCheckMitigation(versionedPkg, - PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, - 1)); - } - } - } - } - }); - } - - /** - * Gets PackageInfo for the given package. Matches any user and apex. - * - * @throws PackageManager.NameNotFoundException if no such package is installed. - */ - private PackageInfo getPackageInfo(String packageName) - throws PackageManager.NameNotFoundException { - PackageManager pm = mContext.getPackageManager(); - try { - // The MATCH_ANY_USER flag doesn't mix well with the MATCH_APEX - // flag, so make two separate attempts to get the package info. - // We don't need both flags at the same time because we assume - // apex files are always installed for all users. - return pm.getPackageInfo(packageName, PackageManager.MATCH_ANY_USER); - } catch (PackageManager.NameNotFoundException e) { - return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); - } - } - - @Nullable - private VersionedPackage getVersionedPackage(String packageName) { - final PackageManager pm = mContext.getPackageManager(); - if (pm == null || TextUtils.isEmpty(packageName)) { - return null; - } - try { - final long versionCode = getPackageInfo(packageName).getLongVersionCode(); - return new VersionedPackage(packageName, versionCode); - } catch (PackageManager.NameNotFoundException e) { - return null; - } - } - - /** - * Loads mAllObservers from file. - * - * <p>Note that this is <b>not</b> thread safe and should only called be called - * from the constructor. - */ - private void loadFromFile() { - InputStream infile = null; - mAllObservers.clear(); - try { - infile = mPolicyFile.openRead(); - final XmlPullParser parser = Xml.newPullParser(); - parser.setInput(infile, UTF_8.name()); - XmlUtils.beginDocument(parser, TAG_PACKAGE_WATCHDOG); - int outerDepth = parser.getDepth(); - while (XmlUtils.nextElementWithin(parser, outerDepth)) { - ObserverInternal observer = ObserverInternal.read(parser, this); - if (observer != null) { - mAllObservers.put(observer.name, observer); - } - } - } catch (FileNotFoundException e) { - // Nothing to monitor - } catch (Exception e) { - Slog.wtf(TAG, "Unable to read monitored packages, deleting file", e); - mPolicyFile.delete(); - } finally { - IoUtils.closeQuietly(infile); - } - } - - private void onPropertyChanged(DeviceConfig.Properties properties) { - try { - updateConfigs(); - } catch (Exception ignore) { - Slog.w(TAG, "Failed to reload device config changes"); - } - } - - /** Adds a {@link DeviceConfig#OnPropertiesChangedListener}. */ - private void setPropertyChangedListenerLocked() { - DeviceConfig.addOnPropertiesChangedListener( - DeviceConfig.NAMESPACE_ROLLBACK, - mContext.getMainExecutor(), - mOnPropertyChangedListener); - } - - @VisibleForTesting - void removePropertyChangedListener() { - DeviceConfig.removeOnPropertiesChangedListener(mOnPropertyChangedListener); - } - - /** - * Health check is enabled or disabled after reading the flags - * from DeviceConfig. - */ - @VisibleForTesting - void updateConfigs() { - synchronized (sLock) { - mTriggerFailureCount = DeviceConfig.getInt( - DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT, - DEFAULT_TRIGGER_FAILURE_COUNT); - if (mTriggerFailureCount <= 0) { - mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT; - } - - mTriggerFailureDurationMs = DeviceConfig.getInt( - DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS, - DEFAULT_TRIGGER_FAILURE_DURATION_MS); - if (mTriggerFailureDurationMs <= 0) { - mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS; - } - - setExplicitHealthCheckEnabled(DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED, - DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED)); - } - } - - /** - * Persists mAllObservers to file. Threshold information is ignored. - */ - private boolean saveToFile() { - Slog.i(TAG, "Saving observer state to file"); - synchronized (sLock) { - FileOutputStream stream; - try { - stream = mPolicyFile.startWrite(); - } catch (IOException e) { - Slog.w(TAG, "Cannot update monitored packages", e); - return false; - } - - try { - XmlSerializer out = new FastXmlSerializer(); - out.setOutput(stream, UTF_8.name()); - out.startDocument(null, true); - out.startTag(null, TAG_PACKAGE_WATCHDOG); - out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION)); - for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) { - mAllObservers.valueAt(oIndex).writeLocked(out); - } - out.endTag(null, TAG_PACKAGE_WATCHDOG); - out.endDocument(); - mPolicyFile.finishWrite(stream); - return true; - } catch (IOException e) { - Slog.w(TAG, "Failed to save monitored packages, restoring backup", e); - mPolicyFile.failWrite(stream); - return false; - } - } - } - - private void saveToFileAsync() { - if (!mLongTaskHandler.hasCallbacks(mSaveToFile)) { - mLongTaskHandler.post(mSaveToFile); - } - } - - /** @hide Convert a {@code LongArrayQueue} to a String of comma-separated values. */ - public static String longArrayQueueToString(LongArrayQueue queue) { - if (queue.size() > 0) { - StringBuilder sb = new StringBuilder(); - sb.append(queue.get(0)); - for (int i = 1; i < queue.size(); i++) { - sb.append(","); - sb.append(queue.get(i)); - } - return sb.toString(); - } - return ""; - } - - /** @hide Parse a comma-separated String of longs into a LongArrayQueue. */ - public static LongArrayQueue parseLongArrayQueue(String commaSeparatedValues) { - LongArrayQueue result = new LongArrayQueue(); - if (!TextUtils.isEmpty(commaSeparatedValues)) { - String[] values = commaSeparatedValues.split(","); - for (String value : values) { - result.addLast(Long.parseLong(value)); - } - } - return result; - } - - - /** Dump status of every observer in mAllObservers. */ - public void dump(@NonNull PrintWriter pw) { - if (Flags.synchronousRebootInRescueParty() && isRecoveryTriggeredReboot()) { - dumpInternal(pw); - } else { - synchronized (sLock) { - dumpInternal(pw); - } - } - } - - /** - * Check if we're currently attempting to reboot during mitigation. This method must return - * true if triggered reboot early during a boot loop, since the device will not be fully booted - * at this time. - */ - public static boolean isRecoveryTriggeredReboot() { - return isFactoryResetPropertySet() || isRebootPropertySet(); - } - - private static boolean isFactoryResetPropertySet() { - return CrashRecoveryProperties.attemptingFactoryReset().orElse(false); - } - - private static boolean isRebootPropertySet() { - return CrashRecoveryProperties.attemptingReboot().orElse(false); - } - - private void dumpInternal(@NonNull PrintWriter pw) { - IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); - ipw.println("Package Watchdog status"); - ipw.increaseIndent(); - synchronized (sLock) { - for (String observerName : mAllObservers.keySet()) { - ipw.println("Observer name: " + observerName); - ipw.increaseIndent(); - ObserverInternal observerInternal = mAllObservers.get(observerName); - observerInternal.dump(ipw); - ipw.decreaseIndent(); - } - } - ipw.decreaseIndent(); - dumpCrashRecoveryEvents(ipw); - } - - @VisibleForTesting - @GuardedBy("sLock") - void registerObserverInternal(ObserverInternal observerInternal) { - mAllObservers.put(observerInternal.name, observerInternal); - } - - /** - * Represents an observer monitoring a set of packages along with the failure thresholds for - * each package. - * - * <p> Note, the PackageWatchdog#sLock must always be held when reading or writing - * instances of this class. - */ - static class ObserverInternal { - public final String name; - @GuardedBy("sLock") - private final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>(); - @Nullable - @GuardedBy("sLock") - public PackageHealthObserver registeredObserver; - public Executor observerExecutor; - private int mMitigationCount; - - ObserverInternal(String name, List<MonitoredPackage> packages) { - this(name, packages, /*mitigationCount=*/ 0); - } - - ObserverInternal(String name, List<MonitoredPackage> packages, int mitigationCount) { - this.name = name; - updatePackagesLocked(packages); - this.mMitigationCount = mitigationCount; - } - - /** - * Writes important {@link MonitoredPackage} details for this observer to file. - * Does not persist any package failure thresholds. - */ - @GuardedBy("sLock") - public boolean writeLocked(XmlSerializer out) { - try { - out.startTag(null, TAG_OBSERVER); - out.attribute(null, ATTR_NAME, name); - if (Flags.recoverabilityDetection()) { - out.attribute(null, ATTR_MITIGATION_COUNT, Integer.toString(mMitigationCount)); - } - for (int i = 0; i < mPackages.size(); i++) { - MonitoredPackage p = mPackages.valueAt(i); - p.writeLocked(out); - } - out.endTag(null, TAG_OBSERVER); - return true; - } catch (IOException e) { - Slog.w(TAG, "Cannot save observer", e); - return false; - } - } - - public int getBootMitigationCount() { - return mMitigationCount; - } - - public void setBootMitigationCount(int mitigationCount) { - mMitigationCount = mitigationCount; - } - - @GuardedBy("sLock") - public void updatePackagesLocked(List<MonitoredPackage> packages) { - for (int pIndex = 0; pIndex < packages.size(); pIndex++) { - MonitoredPackage p = packages.get(pIndex); - MonitoredPackage existingPackage = getMonitoredPackage(p.getName()); - if (existingPackage != null) { - existingPackage.updateHealthCheckDuration(p.mDurationMs); - } else { - putMonitoredPackage(p); - } - } - } - - /** - * Reduces the monitoring durations of all packages observed by this observer by - * {@code elapsedMs}. If any duration is less than 0, the package is removed from - * observation. If any health check duration is less than 0, the health check result - * is evaluated. - * - * @return a {@link Set} of packages that were removed from the observer without explicit - * health check passing, or an empty list if no package expired for which an explicit health - * check was still pending - */ - @GuardedBy("sLock") - private Set<MonitoredPackage> prunePackagesLocked(long elapsedMs) { - Set<MonitoredPackage> failedPackages = new ArraySet<>(); - Iterator<MonitoredPackage> it = mPackages.values().iterator(); - while (it.hasNext()) { - MonitoredPackage p = it.next(); - int oldState = p.getHealthCheckStateLocked(); - int newState = p.handleElapsedTimeLocked(elapsedMs); - if (oldState != HealthCheckState.FAILED - && newState == HealthCheckState.FAILED) { - Slog.i(TAG, "Package " + p.getName() + " failed health check"); - failedPackages.add(p); - } - if (p.isExpiredLocked()) { - it.remove(); - } - } - return failedPackages; - } - - /** - * Increments failure counts of {@code packageName}. - * @returns {@code true} if failure threshold is exceeded, {@code false} otherwise - * @hide - */ - @GuardedBy("sLock") - public boolean notifyPackageFailureLocked(String packageName) { - if (getMonitoredPackage(packageName) == null && registeredObserver.isPersistent() - && registeredObserver.mayObservePackage(packageName)) { - putMonitoredPackage(sPackageWatchdog.newMonitoredPackage( - packageName, DEFAULT_OBSERVING_DURATION_MS, false)); - } - MonitoredPackage p = getMonitoredPackage(packageName); - if (p != null) { - return p.onFailureLocked(); - } - return false; - } - - /** - * Returns the map of packages monitored by this observer. - * - * @return a mapping of package names to {@link MonitoredPackage} objects. - */ - @GuardedBy("sLock") - public ArrayMap<String, MonitoredPackage> getMonitoredPackages() { - return mPackages; - } - - /** - * Returns the {@link MonitoredPackage} associated with a given package name if the - * package is being monitored by this observer. - * - * @param packageName: the name of the package. - * @return the {@link MonitoredPackage} object associated with the package name if one - * exists, {@code null} otherwise. - */ - @GuardedBy("sLock") - @Nullable - public MonitoredPackage getMonitoredPackage(String packageName) { - return mPackages.get(packageName); - } - - /** - * Associates a {@link MonitoredPackage} with the observer. - * - * @param p: the {@link MonitoredPackage} to store. - */ - @GuardedBy("sLock") - public void putMonitoredPackage(MonitoredPackage p) { - mPackages.put(p.getName(), p); - } - - /** - * Returns one ObserverInternal from the {@code parser} and advances its state. - * - * <p>Note that this method is <b>not</b> thread safe. It should only be called from - * #loadFromFile which in turn is only called on construction of the - * singleton PackageWatchdog. - **/ - public static ObserverInternal read(XmlPullParser parser, PackageWatchdog watchdog) { - String observerName = null; - int observerMitigationCount = 0; - if (TAG_OBSERVER.equals(parser.getName())) { - observerName = parser.getAttributeValue(null, ATTR_NAME); - if (TextUtils.isEmpty(observerName)) { - Slog.wtf(TAG, "Unable to read observer name"); - return null; - } - } - List<MonitoredPackage> packages = new ArrayList<>(); - int innerDepth = parser.getDepth(); - try { - if (Flags.recoverabilityDetection()) { - try { - observerMitigationCount = Integer.parseInt( - parser.getAttributeValue(null, ATTR_MITIGATION_COUNT)); - } catch (Exception e) { - Slog.i( - TAG, - "ObserverInternal mitigation count was not present."); - } - } - while (XmlUtils.nextElementWithin(parser, innerDepth)) { - if (TAG_PACKAGE.equals(parser.getName())) { - try { - MonitoredPackage pkg = watchdog.parseMonitoredPackage(parser); - if (pkg != null) { - packages.add(pkg); - } - } catch (NumberFormatException e) { - Slog.wtf(TAG, "Skipping package for observer " + observerName, e); - continue; - } - } - } - } catch (XmlPullParserException | IOException e) { - Slog.wtf(TAG, "Unable to read observer " + observerName, e); - return null; - } - if (packages.isEmpty()) { - return null; - } - return new ObserverInternal(observerName, packages, observerMitigationCount); - } - - /** Dumps information about this observer and the packages it watches. */ - public void dump(IndentingPrintWriter pw) { - boolean isPersistent = registeredObserver != null && registeredObserver.isPersistent(); - pw.println("Persistent: " + isPersistent); - for (String packageName : mPackages.keySet()) { - MonitoredPackage p = getMonitoredPackage(packageName); - pw.println(packageName + ": "); - pw.increaseIndent(); - pw.println("# Failures: " + p.mFailureHistory.size()); - pw.println("Monitoring duration remaining: " + p.mDurationMs + "ms"); - pw.println("Explicit health check duration: " + p.mHealthCheckDurationMs + "ms"); - pw.println("Health check state: " + p.toString(p.mHealthCheckState)); - pw.decreaseIndent(); - } - } - } - - /** @hide */ - @Retention(SOURCE) - @IntDef(value = { - HealthCheckState.ACTIVE, - HealthCheckState.INACTIVE, - HealthCheckState.PASSED, - HealthCheckState.FAILED}) - public @interface HealthCheckState { - // The package has not passed health check but has requested a health check - int ACTIVE = 0; - // The package has not passed health check and has not requested a health check - int INACTIVE = 1; - // The package has passed health check - int PASSED = 2; - // The package has failed health check - int FAILED = 3; - } - - MonitoredPackage newMonitoredPackage( - String name, long durationMs, boolean hasPassedHealthCheck) { - return newMonitoredPackage(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck, - new LongArrayQueue()); - } - - MonitoredPackage newMonitoredPackage(String name, long durationMs, long healthCheckDurationMs, - boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) { - return new MonitoredPackage(name, durationMs, healthCheckDurationMs, - hasPassedHealthCheck, mitigationCalls); - } - - MonitoredPackage parseMonitoredPackage(XmlPullParser parser) - throws XmlPullParserException { - String packageName = parser.getAttributeValue(null, ATTR_NAME); - long duration = Long.parseLong(parser.getAttributeValue(null, ATTR_DURATION)); - long healthCheckDuration = Long.parseLong(parser.getAttributeValue(null, - ATTR_EXPLICIT_HEALTH_CHECK_DURATION)); - boolean hasPassedHealthCheck = Boolean.parseBoolean(parser.getAttributeValue(null, - ATTR_PASSED_HEALTH_CHECK)); - LongArrayQueue mitigationCalls = parseLongArrayQueue( - parser.getAttributeValue(null, ATTR_MITIGATION_CALLS)); - return newMonitoredPackage(packageName, - duration, healthCheckDuration, hasPassedHealthCheck, mitigationCalls); - } - - /** - * Represents a package and its health check state along with the time - * it should be monitored for. - * - * <p> Note, the PackageWatchdog#sLock must always be held when reading or writing - * instances of this class. - */ - class MonitoredPackage { - private final String mPackageName; - // Times when package failures happen sorted in ascending order - @GuardedBy("sLock") - private final LongArrayQueue mFailureHistory = new LongArrayQueue(); - // Times when an observer was called to mitigate this package's failure. Sorted in - // ascending order. - @GuardedBy("sLock") - private final LongArrayQueue mMitigationCalls; - // One of STATE_[ACTIVE|INACTIVE|PASSED|FAILED]. Updated on construction and after - // methods that could change the health check state: handleElapsedTimeLocked and - // tryPassHealthCheckLocked - private int mHealthCheckState = HealthCheckState.INACTIVE; - // Whether an explicit health check has passed. - // This value in addition with mHealthCheckDurationMs determines the health check state - // of the package, see #getHealthCheckStateLocked - @GuardedBy("sLock") - private boolean mHasPassedHealthCheck; - // System uptime duration to monitor package. - @GuardedBy("sLock") - private long mDurationMs; - // System uptime duration to check the result of an explicit health check - // Initially, MAX_VALUE until we get a value from the health check service - // and request health checks. - // This value in addition with mHasPassedHealthCheck determines the health check state - // of the package, see #getHealthCheckStateLocked - @GuardedBy("sLock") - private long mHealthCheckDurationMs = Long.MAX_VALUE; - - MonitoredPackage(String packageName, long durationMs, - long healthCheckDurationMs, boolean hasPassedHealthCheck, - LongArrayQueue mitigationCalls) { - mPackageName = packageName; - mDurationMs = durationMs; - mHealthCheckDurationMs = healthCheckDurationMs; - mHasPassedHealthCheck = hasPassedHealthCheck; - mMitigationCalls = mitigationCalls; - updateHealthCheckStateLocked(); - } - - /** Writes the salient fields to disk using {@code out}. - * @hide - */ - @GuardedBy("sLock") - public void writeLocked(XmlSerializer out) throws IOException { - out.startTag(null, TAG_PACKAGE); - out.attribute(null, ATTR_NAME, getName()); - out.attribute(null, ATTR_DURATION, Long.toString(mDurationMs)); - out.attribute(null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION, - Long.toString(mHealthCheckDurationMs)); - out.attribute(null, ATTR_PASSED_HEALTH_CHECK, Boolean.toString(mHasPassedHealthCheck)); - LongArrayQueue normalizedCalls = normalizeMitigationCalls(); - out.attribute(null, ATTR_MITIGATION_CALLS, longArrayQueueToString(normalizedCalls)); - out.endTag(null, TAG_PACKAGE); - } - - /** - * Increment package failures or resets failure count depending on the last package failure. - * - * @return {@code true} if failure count exceeds a threshold, {@code false} otherwise - */ - @GuardedBy("sLock") - public boolean onFailureLocked() { - // Sliding window algorithm: find out if there exists a window containing failures >= - // mTriggerFailureCount. - final long now = mSystemClock.uptimeMillis(); - mFailureHistory.addLast(now); - while (now - mFailureHistory.peekFirst() > mTriggerFailureDurationMs) { - // Prune values falling out of the window - mFailureHistory.removeFirst(); - } - boolean failed = mFailureHistory.size() >= mTriggerFailureCount; - if (failed) { - mFailureHistory.clear(); - } - return failed; - } - - /** - * Notes the timestamp of a mitigation call into the observer. - */ - @GuardedBy("sLock") - public void noteMitigationCallLocked() { - mMitigationCalls.addLast(mSystemClock.uptimeMillis()); - } - - /** - * Prunes any mitigation calls outside of the de-escalation window, and returns the - * number of calls that are in the window afterwards. - * - * @return the number of mitigation calls made in the de-escalation window. - */ - @GuardedBy("sLock") - public int getMitigationCountLocked() { - try { - final long now = mSystemClock.uptimeMillis(); - while (now - mMitigationCalls.peekFirst() > DEFAULT_DEESCALATION_WINDOW_MS) { - mMitigationCalls.removeFirst(); - } - } catch (NoSuchElementException ignore) { - } - - return mMitigationCalls.size(); - } - - /** - * Before writing to disk, make the mitigation call timestamps relative to the current - * system uptime. This is because they need to be relative to the uptime which will reset - * at the next boot. - * - * @return a LongArrayQueue of the mitigation calls relative to the current system uptime. - */ - @GuardedBy("sLock") - public LongArrayQueue normalizeMitigationCalls() { - LongArrayQueue normalized = new LongArrayQueue(); - final long now = mSystemClock.uptimeMillis(); - for (int i = 0; i < mMitigationCalls.size(); i++) { - normalized.addLast(mMitigationCalls.get(i) - now); - } - return normalized; - } - - /** - * Sets the initial health check duration. - * - * @return the new health check state - */ - @GuardedBy("sLock") - public int setHealthCheckActiveLocked(long initialHealthCheckDurationMs) { - if (initialHealthCheckDurationMs <= 0) { - Slog.wtf(TAG, "Cannot set non-positive health check duration " - + initialHealthCheckDurationMs + "ms for package " + getName() - + ". Using total duration " + mDurationMs + "ms instead"); - initialHealthCheckDurationMs = mDurationMs; - } - if (mHealthCheckState == HealthCheckState.INACTIVE) { - // Transitions to ACTIVE - mHealthCheckDurationMs = initialHealthCheckDurationMs; - } - return updateHealthCheckStateLocked(); - } - - /** - * Updates the monitoring durations of the package. - * - * @return the new health check state - */ - @GuardedBy("sLock") - public int handleElapsedTimeLocked(long elapsedMs) { - if (elapsedMs <= 0) { - Slog.w(TAG, "Cannot handle non-positive elapsed time for package " + getName()); - return mHealthCheckState; - } - // Transitions to FAILED if now <= 0 and health check not passed - mDurationMs -= elapsedMs; - if (mHealthCheckState == HealthCheckState.ACTIVE) { - // We only update health check durations if we have #setHealthCheckActiveLocked - // This ensures we don't leave the INACTIVE state for an unexpected elapsed time - // Transitions to FAILED if now <= 0 and health check not passed - mHealthCheckDurationMs -= elapsedMs; - } - return updateHealthCheckStateLocked(); - } - - /** Explicitly update the monitoring duration of the package. */ - @GuardedBy("sLock") - public void updateHealthCheckDuration(long newDurationMs) { - mDurationMs = newDurationMs; - } - - /** - * Marks the health check as passed and transitions to {@link HealthCheckState.PASSED} - * if not yet {@link HealthCheckState.FAILED}. - * - * @return the new {@link HealthCheckState health check state} - */ - @GuardedBy("sLock") - @HealthCheckState - public int tryPassHealthCheckLocked() { - if (mHealthCheckState != HealthCheckState.FAILED) { - // FAILED is a final state so only pass if we haven't failed - // Transition to PASSED - mHasPassedHealthCheck = true; - } - return updateHealthCheckStateLocked(); - } - - /** Returns the monitored package name. */ - private String getName() { - return mPackageName; - } - - /** - * Returns the current {@link HealthCheckState health check state}. - */ - @GuardedBy("sLock") - @HealthCheckState - public int getHealthCheckStateLocked() { - return mHealthCheckState; - } - - /** - * Returns the shortest duration before the package should be scheduled for a prune. - * - * @return the duration or {@link Long#MAX_VALUE} if the package should not be scheduled - */ - @GuardedBy("sLock") - public long getShortestScheduleDurationMsLocked() { - // Consider health check duration only if #isPendingHealthChecksLocked is true - return Math.min(toPositive(mDurationMs), - isPendingHealthChecksLocked() - ? toPositive(mHealthCheckDurationMs) : Long.MAX_VALUE); - } - - /** - * Returns {@code true} if the total duration left to monitor the package is less than or - * equal to 0 {@code false} otherwise. - */ - @GuardedBy("sLock") - public boolean isExpiredLocked() { - return mDurationMs <= 0; - } - - /** - * Returns {@code true} if the package, {@link #getName} is expecting health check results - * {@code false} otherwise. - */ - @GuardedBy("sLock") - public boolean isPendingHealthChecksLocked() { - return mHealthCheckState == HealthCheckState.ACTIVE - || mHealthCheckState == HealthCheckState.INACTIVE; - } - - /** - * Updates the health check state based on {@link #mHasPassedHealthCheck} - * and {@link #mHealthCheckDurationMs}. - * - * @return the new {@link HealthCheckState health check state} - */ - @GuardedBy("sLock") - @HealthCheckState - private int updateHealthCheckStateLocked() { - int oldState = mHealthCheckState; - if (mHasPassedHealthCheck) { - // Set final state first to avoid ambiguity - mHealthCheckState = HealthCheckState.PASSED; - } else if (mHealthCheckDurationMs <= 0 || mDurationMs <= 0) { - // Set final state first to avoid ambiguity - mHealthCheckState = HealthCheckState.FAILED; - } else if (mHealthCheckDurationMs == Long.MAX_VALUE) { - mHealthCheckState = HealthCheckState.INACTIVE; - } else { - mHealthCheckState = HealthCheckState.ACTIVE; - } - - if (oldState != mHealthCheckState) { - Slog.i(TAG, "Updated health check state for package " + getName() + ": " - + toString(oldState) + " -> " + toString(mHealthCheckState)); - } - return mHealthCheckState; - } - - /** Returns a {@link String} representation of the current health check state. */ - private String toString(@HealthCheckState int state) { - switch (state) { - case HealthCheckState.ACTIVE: - return "ACTIVE"; - case HealthCheckState.INACTIVE: - return "INACTIVE"; - case HealthCheckState.PASSED: - return "PASSED"; - case HealthCheckState.FAILED: - return "FAILED"; - default: - return "UNKNOWN"; - } - } - - /** Returns {@code value} if it is greater than 0 or {@link Long#MAX_VALUE} otherwise. */ - private long toPositive(long value) { - return value > 0 ? value : Long.MAX_VALUE; - } - - /** Compares the equality of this object with another {@link MonitoredPackage}. */ - @VisibleForTesting - boolean isEqualTo(MonitoredPackage pkg) { - return (getName().equals(pkg.getName())) - && mDurationMs == pkg.mDurationMs - && mHasPassedHealthCheck == pkg.mHasPassedHealthCheck - && mHealthCheckDurationMs == pkg.mHealthCheckDurationMs - && (mMitigationCalls.toString()).equals(pkg.mMitigationCalls.toString()); - } - } - - @GuardedBy("sLock") - @SuppressWarnings("GuardedBy") - void saveAllObserversBootMitigationCountToMetadata(String filePath) { - HashMap<String, Integer> bootMitigationCounts = new HashMap<>(); - for (int i = 0; i < mAllObservers.size(); i++) { - final ObserverInternal observer = mAllObservers.valueAt(i); - bootMitigationCounts.put(observer.name, observer.getBootMitigationCount()); - } - - FileOutputStream fileStream = null; - ObjectOutputStream objectStream = null; - try { - fileStream = new FileOutputStream(new File(filePath)); - objectStream = new ObjectOutputStream(fileStream); - objectStream.writeObject(bootMitigationCounts); - objectStream.flush(); - } catch (Exception e) { - Slog.i(TAG, "Could not save observers metadata to file: " + e); - return; - } finally { - IoUtils.closeQuietly(objectStream); - IoUtils.closeQuietly(fileStream); - } - } - - /** - * Handles the thresholding logic for system server boots. - */ - class BootThreshold { - - private final int mBootTriggerCount; - private final long mTriggerWindow; - - BootThreshold(int bootTriggerCount, long triggerWindow) { - this.mBootTriggerCount = bootTriggerCount; - this.mTriggerWindow = triggerWindow; - } - - public void reset() { - setStart(0); - setCount(0); - } - - protected int getCount() { - return CrashRecoveryProperties.rescueBootCount().orElse(0); - } - - protected void setCount(int count) { - CrashRecoveryProperties.rescueBootCount(count); - } - - public long getStart() { - return CrashRecoveryProperties.rescueBootStart().orElse(0L); - } - - public int getMitigationCount() { - return CrashRecoveryProperties.bootMitigationCount().orElse(0); - } - - public void setStart(long start) { - CrashRecoveryProperties.rescueBootStart(getStartTime(start)); - } - - public void setMitigationStart(long start) { - CrashRecoveryProperties.bootMitigationStart(getStartTime(start)); - } - - public long getMitigationStart() { - return CrashRecoveryProperties.bootMitigationStart().orElse(0L); - } - - public void setMitigationCount(int count) { - CrashRecoveryProperties.bootMitigationCount(count); - } - - private static long constrain(long amount, long low, long high) { - return amount < low ? low : (amount > high ? high : amount); - } - - public long getStartTime(long start) { - final long now = mSystemClock.uptimeMillis(); - return constrain(start, 0, now); - } - - public void saveMitigationCountToMetadata() { - try (BufferedWriter writer = new BufferedWriter(new FileWriter(METADATA_FILE))) { - writer.write(String.valueOf(getMitigationCount())); - } catch (Exception e) { - Slog.e(TAG, "Could not save metadata to file: " + e); - } - } - - public void readMitigationCountFromMetadataIfNecessary() { - File bootPropsFile = new File(METADATA_FILE); - if (bootPropsFile.exists()) { - try (BufferedReader reader = new BufferedReader(new FileReader(METADATA_FILE))) { - String mitigationCount = reader.readLine(); - setMitigationCount(Integer.parseInt(mitigationCount)); - bootPropsFile.delete(); - } catch (Exception e) { - Slog.i(TAG, "Could not read metadata file: " + e); - } - } - } - - - /** Increments the boot counter, and returns whether the device is bootlooping. */ - @GuardedBy("sLock") - public boolean incrementAndTest() { - if (Flags.recoverabilityDetection()) { - readAllObserversBootMitigationCountIfNecessary(METADATA_FILE); - } else { - readMitigationCountFromMetadataIfNecessary(); - } - - final long now = mSystemClock.uptimeMillis(); - if (now - getStart() < 0) { - Slog.e(TAG, "Window was less than zero. Resetting start to current time."); - setStart(now); - setMitigationStart(now); - } - if (now - getMitigationStart() > DEFAULT_DEESCALATION_WINDOW_MS) { - setMitigationStart(now); - if (Flags.recoverabilityDetection()) { - resetAllObserversBootMitigationCount(); - } else { - setMitigationCount(0); - } - } - final long window = now - getStart(); - if (window >= mTriggerWindow) { - setCount(1); - setStart(now); - return false; - } else { - int count = getCount() + 1; - setCount(count); - EventLog.writeEvent(LOG_TAG_RESCUE_NOTE, Process.ROOT_UID, count, window); - if (Flags.recoverabilityDetection()) { - // After a reboot (e.g. by WARM_REBOOT or mainline rollback) we apply - // mitigations without waiting for DEFAULT_BOOT_LOOP_TRIGGER_COUNT. - return (count >= mBootTriggerCount) - || (performedMitigationsDuringWindow() && count > 1); - } - return count >= mBootTriggerCount; - } - } - - @GuardedBy("sLock") - private boolean performedMitigationsDuringWindow() { - for (ObserverInternal observerInternal: mAllObservers.values()) { - if (observerInternal.getBootMitigationCount() > 0) { - return true; - } - } - return false; - } - - @GuardedBy("sLock") - private void resetAllObserversBootMitigationCount() { - for (int i = 0; i < mAllObservers.size(); i++) { - final ObserverInternal observer = mAllObservers.valueAt(i); - observer.setBootMitigationCount(0); - } - saveAllObserversBootMitigationCountToMetadata(METADATA_FILE); - } - - @GuardedBy("sLock") - @SuppressWarnings("GuardedBy") - void readAllObserversBootMitigationCountIfNecessary(String filePath) { - File metadataFile = new File(filePath); - if (metadataFile.exists()) { - FileInputStream fileStream = null; - ObjectInputStream objectStream = null; - HashMap<String, Integer> bootMitigationCounts = null; - try { - fileStream = new FileInputStream(metadataFile); - objectStream = new ObjectInputStream(fileStream); - bootMitigationCounts = - (HashMap<String, Integer>) objectStream.readObject(); - } catch (Exception e) { - Slog.i(TAG, "Could not read observer metadata file: " + e); - return; - } finally { - IoUtils.closeQuietly(objectStream); - IoUtils.closeQuietly(fileStream); - } - - if (bootMitigationCounts == null || bootMitigationCounts.isEmpty()) { - Slog.i(TAG, "No observer in metadata file"); - return; - } - for (int i = 0; i < mAllObservers.size(); i++) { - final ObserverInternal observer = mAllObservers.valueAt(i); - if (bootMitigationCounts.containsKey(observer.name)) { - observer.setBootMitigationCount( - bootMitigationCounts.get(observer.name)); - } - } - } - } - } - - /** - * Register broadcast receiver for shutdown. - * We would save the observer state to persist across boots. - * - * @hide - */ - public void registerShutdownBroadcastReceiver() { - BroadcastReceiver shutdownEventReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // Only write if intent is relevant to device reboot or shutdown. - String intentAction = intent.getAction(); - if (ACTION_REBOOT.equals(intentAction) - || ACTION_SHUTDOWN.equals(intentAction)) { - writeNow(); - } - } - }; - - // Setup receiver for device reboots or shutdowns. - IntentFilter filter = new IntentFilter(ACTION_REBOOT); - filter.addAction(ACTION_SHUTDOWN); - mContext.registerReceiverForAllUsers(shutdownEventReceiver, filter, null, - /* run on main thread */ null); - } -} diff --git a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java deleted file mode 100644 index 846da194b3c3..000000000000 --- a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java +++ /dev/null @@ -1,861 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED; -import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS; -import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.VersionedPackage; -import android.crashrecovery.flags.Flags; -import android.os.Build; -import android.os.PowerManager; -import android.os.RecoverySystem; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.provider.Settings; -import android.sysprop.CrashRecoveryProperties; -import android.text.TextUtils; -import android.util.EventLog; -import android.util.FileUtils; -import android.util.Log; -import android.util.Slog; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.PackageWatchdog.FailureReasons; -import com.android.server.PackageWatchdog.PackageHealthObserver; -import com.android.server.PackageWatchdog.PackageHealthObserverImpact; -import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog; - -import java.io.File; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -/** - * Utilities to help rescue the system from crash loops. Callers are expected to - * report boot events and persistent app crashes, and if they happen frequently - * enough this class will slowly escalate through several rescue operations - * before finally rebooting and prompting the user if they want to wipe data as - * a last resort. - * - * @hide - */ -public class RescueParty { - @VisibleForTesting - static final String PROP_ENABLE_RESCUE = "persist.sys.enable_rescue"; - @VisibleForTesting - static final int LEVEL_NONE = 0; - @VisibleForTesting - static final int LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 1; - @VisibleForTesting - static final int LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 2; - @VisibleForTesting - static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3; - @VisibleForTesting - static final int LEVEL_WARM_REBOOT = 4; - @VisibleForTesting - static final int LEVEL_FACTORY_RESET = 5; - @VisibleForTesting - static final int RESCUE_LEVEL_NONE = 0; - @VisibleForTesting - static final int RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET = 1; - @VisibleForTesting - static final int RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET = 2; - @VisibleForTesting - static final int RESCUE_LEVEL_WARM_REBOOT = 3; - @VisibleForTesting - static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 4; - @VisibleForTesting - static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 5; - @VisibleForTesting - static final int RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 6; - @VisibleForTesting - static final int RESCUE_LEVEL_FACTORY_RESET = 7; - - @IntDef(prefix = { "RESCUE_LEVEL_" }, value = { - RESCUE_LEVEL_NONE, - RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET, - RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET, - RESCUE_LEVEL_WARM_REBOOT, - RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS, - RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES, - RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS, - RESCUE_LEVEL_FACTORY_RESET - }) - @Retention(RetentionPolicy.SOURCE) - @interface RescueLevels {} - - @VisibleForTesting - static final String RESCUE_NON_REBOOT_LEVEL_LIMIT = "persist.sys.rescue_non_reboot_level_limit"; - @VisibleForTesting - static final int DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT = RESCUE_LEVEL_WARM_REBOOT - 1; - @VisibleForTesting - static final String TAG = "RescueParty"; - @VisibleForTesting - static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2); - @VisibleForTesting - static final int DEVICE_CONFIG_RESET_MODE = Settings.RESET_MODE_TRUSTED_DEFAULTS; - // The DeviceConfig namespace containing all RescueParty switches. - @VisibleForTesting - static final String NAMESPACE_CONFIGURATION = "configuration"; - @VisibleForTesting - static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG = - "namespace_to_package_mapping"; - @VisibleForTesting - static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 1440; - - private static final String NAME = "rescue-party-observer"; - - private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; - private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device"; - private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG = - "persist.device_config.configuration.disable_rescue_party"; - private static final String PROP_DISABLE_FACTORY_RESET_FLAG = - "persist.device_config.configuration.disable_rescue_party_factory_reset"; - private static final String PROP_THROTTLE_DURATION_MIN_FLAG = - "persist.device_config.configuration.rescue_party_throttle_duration_min"; - - private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT - | ApplicationInfo.FLAG_SYSTEM; - - /** - * EventLog tags used when logging into the event log. Note the values must be sync with - * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct - * name translation. - */ - private static final int LOG_TAG_RESCUE_SUCCESS = 2902; - private static final int LOG_TAG_RESCUE_FAILURE = 2903; - - /** Register the Rescue Party observer as a Package Watchdog health observer */ - public static void registerHealthObserver(Context context) { - PackageWatchdog.getInstance(context).registerHealthObserver( - context.getMainExecutor(), RescuePartyObserver.getInstance(context)); - } - - private static boolean isDisabled() { - // Check if we're explicitly enabled for testing - if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) { - return false; - } - - // We're disabled if the DeviceConfig disable flag is set to true. - // This is in case that an emergency rollback of the feature is needed. - if (SystemProperties.getBoolean(PROP_DEVICE_CONFIG_DISABLE_FLAG, false)) { - Slog.v(TAG, "Disabled because of DeviceConfig flag"); - return true; - } - - // We're disabled on all engineering devices - if (Build.TYPE.equals("eng")) { - Slog.v(TAG, "Disabled because of eng build"); - return true; - } - - // We're disabled on userdebug devices connected over USB, since that's - // a decent signal that someone is actively trying to debug the device, - // or that it's in a lab environment. - if (Build.TYPE.equals("userdebug") && isUsbActive()) { - Slog.v(TAG, "Disabled because of active USB connection"); - return true; - } - - // One last-ditch check - if (SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false)) { - Slog.v(TAG, "Disabled because of manual property"); - return true; - } - - return false; - } - - /** - * Check if we're currently attempting to reboot for a factory reset. This method must - * return true if RescueParty tries to reboot early during a boot loop, since the device - * will not be fully booted at this time. - */ - public static boolean isRecoveryTriggeredReboot() { - return isFactoryResetPropertySet() || isRebootPropertySet(); - } - - static boolean isFactoryResetPropertySet() { - return CrashRecoveryProperties.attemptingFactoryReset().orElse(false); - } - - static boolean isRebootPropertySet() { - return CrashRecoveryProperties.attemptingReboot().orElse(false); - } - - protected static long getLastFactoryResetTimeMs() { - return CrashRecoveryProperties.lastFactoryResetTimeMs().orElse(0L); - } - - protected static int getMaxRescueLevelAttempted() { - return CrashRecoveryProperties.maxRescueLevelAttempted().orElse(LEVEL_NONE); - } - - protected static void setFactoryResetProperty(boolean value) { - CrashRecoveryProperties.attemptingFactoryReset(value); - } - protected static void setRebootProperty(boolean value) { - CrashRecoveryProperties.attemptingReboot(value); - } - - protected static void setLastFactoryResetTimeMs(long value) { - CrashRecoveryProperties.lastFactoryResetTimeMs(value); - } - - protected static void setMaxRescueLevelAttempted(int level) { - CrashRecoveryProperties.maxRescueLevelAttempted(level); - } - - @VisibleForTesting - static long getElapsedRealtime() { - return SystemClock.elapsedRealtime(); - } - - private static int getMaxRescueLevel(boolean mayPerformReboot) { - if (Flags.recoverabilityDetection()) { - if (!mayPerformReboot - || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) { - return SystemProperties.getInt(RESCUE_NON_REBOOT_LEVEL_LIMIT, - DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT); - } - return RESCUE_LEVEL_FACTORY_RESET; - } else { - if (!mayPerformReboot - || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) { - return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS; - } - return LEVEL_FACTORY_RESET; - } - } - - private static int getMaxRescueLevel() { - if (!SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) { - return Level.factoryReset(); - } - return Level.reboot(); - } - - /** - * Get the rescue level to perform if this is the n-th attempt at mitigating failure. - * - * @param mitigationCount: the mitigation attempt number (1 = first attempt etc.) - * @param mayPerformReboot: whether or not a reboot and factory reset may be performed - * for the given failure. - * @return the rescue level for the n-th mitigation attempt. - */ - private static int getRescueLevel(int mitigationCount, boolean mayPerformReboot) { - if (!Flags.deprecateFlagsAndSettingsResets()) { - if (mitigationCount == 1) { - return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS; - } else if (mitigationCount == 2) { - return LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES; - } else if (mitigationCount == 3) { - return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS; - } else if (mitigationCount == 4) { - return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_WARM_REBOOT); - } else if (mitigationCount >= 5) { - return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_FACTORY_RESET); - } else { - Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount); - return LEVEL_NONE; - } - } else { - if (mitigationCount == 1) { - return Level.reboot(); - } else if (mitigationCount >= 2) { - return Math.min(getMaxRescueLevel(), Level.factoryReset()); - } else { - Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount); - return LEVEL_NONE; - } - } - } - - /** - * Get the rescue level to perform if this is the n-th attempt at mitigating failure. - * When failedPackage is null then 1st and 2nd mitigation counts are redundant (scoped and - * all device config reset). Behaves as if one mitigation attempt was already done. - * - * @param mitigationCount the mitigation attempt number (1 = first attempt etc.). - * @param mayPerformReboot whether or not a reboot and factory reset may be performed - * for the given failure. - * @param failedPackage in case of bootloop this is null. - * @return the rescue level for the n-th mitigation attempt. - */ - private static @RescueLevels int getRescueLevel(int mitigationCount, boolean mayPerformReboot, - @Nullable VersionedPackage failedPackage) { - // Skipping RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET since it's not defined without a failed - // package. - if (failedPackage == null && mitigationCount > 0) { - mitigationCount += 1; - } - if (mitigationCount == 1) { - return RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET; - } else if (mitigationCount == 2) { - return RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET; - } else if (mitigationCount == 3) { - return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_WARM_REBOOT); - } else if (mitigationCount == 4) { - return Math.min(getMaxRescueLevel(mayPerformReboot), - RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS); - } else if (mitigationCount == 5) { - return Math.min(getMaxRescueLevel(mayPerformReboot), - RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES); - } else if (mitigationCount == 6) { - return Math.min(getMaxRescueLevel(mayPerformReboot), - RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS); - } else if (mitigationCount >= 7) { - return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_FACTORY_RESET); - } else { - return RESCUE_LEVEL_NONE; - } - } - - /** - * Get the rescue level to perform if this is the n-th attempt at mitigating failure. - * - * @param mitigationCount the mitigation attempt number (1 = first attempt etc.). - * @return the rescue level for the n-th mitigation attempt. - */ - private static @RescueLevels int getRescueLevel(int mitigationCount) { - if (mitigationCount == 1) { - return Level.reboot(); - } else if (mitigationCount >= 2) { - return Math.min(getMaxRescueLevel(), Level.factoryReset()); - } else { - return Level.none(); - } - } - - private static void executeRescueLevel(Context context, @Nullable String failedPackage, - int level) { - Slog.w(TAG, "Attempting rescue level " + levelToString(level)); - try { - executeRescueLevelInternal(context, level, failedPackage); - EventLog.writeEvent(LOG_TAG_RESCUE_SUCCESS, level); - String successMsg = "Finished rescue level " + levelToString(level); - if (!TextUtils.isEmpty(failedPackage)) { - successMsg += " for package " + failedPackage; - } - logCrashRecoveryEvent(Log.DEBUG, successMsg); - } catch (Throwable t) { - logRescueException(level, failedPackage, t); - } - } - - private static void executeRescueLevelInternal(Context context, int level, @Nullable - String failedPackage) throws Exception { - if (Flags.recoverabilityDetection()) { - executeRescueLevelInternalNew(context, level, failedPackage); - } else { - executeRescueLevelInternalOld(context, level, failedPackage); - } - } - - private static void executeRescueLevelInternalOld(Context context, int level, @Nullable - String failedPackage) throws Exception { - CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED, - level, levelToString(level)); - // Try our best to reset all settings possible, and once finished - // rethrow any exception that we encountered - Exception res = null; - switch (level) { - case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: - break; - case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: - break; - case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: - break; - case LEVEL_WARM_REBOOT: - executeWarmReboot(context, level, failedPackage); - break; - case LEVEL_FACTORY_RESET: - // Before the completion of Reboot, if any crash happens then PackageWatchdog - // escalates to next level i.e. factory reset, as they happen in separate threads. - // Adding a check to prevent factory reset to execute before above reboot completes. - // Note: this reboot property is not persistent resets after reboot is completed. - if (isRebootPropertySet()) { - return; - } - executeFactoryReset(context, level, failedPackage); - break; - } - - if (res != null) { - throw res; - } - } - - private static void executeRescueLevelInternalNew(Context context, @RescueLevels int level, - @Nullable String failedPackage) throws Exception { - CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED, - level, levelToString(level)); - switch (level) { - case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET: - break; - case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET: - break; - case RESCUE_LEVEL_WARM_REBOOT: - executeWarmReboot(context, level, failedPackage); - break; - case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: - // do nothing - break; - case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: - // do nothing - break; - case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: - // do nothing - break; - case RESCUE_LEVEL_FACTORY_RESET: - // Before the completion of Reboot, if any crash happens then PackageWatchdog - // escalates to next level i.e. factory reset, as they happen in separate threads. - // Adding a check to prevent factory reset to execute before above reboot completes. - // Note: this reboot property is not persistent resets after reboot is completed. - if (isRebootPropertySet()) { - return; - } - executeFactoryReset(context, level, failedPackage); - break; - } - } - - private static void executeWarmReboot(Context context, int level, - @Nullable String failedPackage) { - if (Flags.deprecateFlagsAndSettingsResets()) { - if (shouldThrottleReboot()) { - return; - } - } - - // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog - // when device shutting down. - setRebootProperty(true); - - if (Flags.synchronousRebootInRescueParty()) { - try { - PowerManager pm = context.getSystemService(PowerManager.class); - if (pm != null) { - pm.reboot(TAG); - } - } catch (Throwable t) { - logRescueException(level, failedPackage, t); - } - } else { - Runnable runnable = () -> { - try { - PowerManager pm = context.getSystemService(PowerManager.class); - if (pm != null) { - pm.reboot(TAG); - } - } catch (Throwable t) { - logRescueException(level, failedPackage, t); - } - }; - Thread thread = new Thread(runnable); - thread.start(); - } - } - - private static void executeFactoryReset(Context context, int level, - @Nullable String failedPackage) { - if (Flags.deprecateFlagsAndSettingsResets()) { - if (shouldThrottleReboot()) { - return; - } - } - setFactoryResetProperty(true); - long now = System.currentTimeMillis(); - setLastFactoryResetTimeMs(now); - - if (Flags.synchronousRebootInRescueParty()) { - try { - RecoverySystem.rebootPromptAndWipeUserData(context, TAG + "," + failedPackage); - } catch (Throwable t) { - logRescueException(level, failedPackage, t); - } - } else { - Runnable runnable = new Runnable() { - @Override - public void run() { - try { - RecoverySystem.rebootPromptAndWipeUserData(context, - TAG + "," + failedPackage); - } catch (Throwable t) { - logRescueException(level, failedPackage, t); - } - } - }; - Thread thread = new Thread(runnable); - thread.start(); - } - } - - - private static String getCompleteMessage(Throwable t) { - final StringBuilder builder = new StringBuilder(); - builder.append(t.getMessage()); - while ((t = t.getCause()) != null) { - builder.append(": ").append(t.getMessage()); - } - return builder.toString(); - } - - private static void logRescueException(int level, @Nullable String failedPackageName, - Throwable t) { - final String msg = getCompleteMessage(t); - EventLog.writeEvent(LOG_TAG_RESCUE_FAILURE, level, msg); - String failureMsg = "Failed rescue level " + levelToString(level); - if (!TextUtils.isEmpty(failedPackageName)) { - failureMsg += " for package " + failedPackageName; - } - logCrashRecoveryEvent(Log.ERROR, failureMsg + ": " + msg); - } - - private static int mapRescueLevelToUserImpact(int rescueLevel) { - if (Flags.recoverabilityDetection()) { - switch (rescueLevel) { - case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10; - case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_40; - case RESCUE_LEVEL_WARM_REBOOT: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50; - case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_71; - case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_75; - case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_80; - case RESCUE_LEVEL_FACTORY_RESET: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100; - default: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; - } - } else { - switch (rescueLevel) { - case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: - case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10; - case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: - case LEVEL_WARM_REBOOT: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50; - case LEVEL_FACTORY_RESET: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100; - default: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; - } - } - } - - /** - * Handle mitigation action for package failures. This observer will be register to Package - * Watchdog and will receive calls about package failures. This observer is persistent so it - * may choose to mitigate failures for packages it has not explicitly asked to observe. - */ - public static class RescuePartyObserver implements PackageHealthObserver { - - private final Context mContext; - private final Map<String, Set<String>> mCallingPackageNamespaceSetMap = new HashMap<>(); - private final Map<String, Set<String>> mNamespaceCallingPackageSetMap = new HashMap<>(); - - @GuardedBy("RescuePartyObserver.class") - static RescuePartyObserver sRescuePartyObserver; - - private RescuePartyObserver(Context context) { - mContext = context; - } - - /** Creates or gets singleton instance of RescueParty. */ - public static RescuePartyObserver getInstance(Context context) { - synchronized (RescuePartyObserver.class) { - if (sRescuePartyObserver == null) { - sRescuePartyObserver = new RescuePartyObserver(context); - } - return sRescuePartyObserver; - } - } - - /** Gets singleton instance. It returns null if the instance is not created yet.*/ - @Nullable - public static RescuePartyObserver getInstanceIfCreated() { - synchronized (RescuePartyObserver.class) { - return sRescuePartyObserver; - } - } - - @VisibleForTesting - static void reset() { - synchronized (RescuePartyObserver.class) { - sRescuePartyObserver = null; - } - } - - @Override - public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage, - @FailureReasons int failureReason, int mitigationCount) { - int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; - if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH - || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) { - if (Flags.recoverabilityDetection()) { - if (!Flags.deprecateFlagsAndSettingsResets()) { - impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, - mayPerformReboot(failedPackage), failedPackage)); - } else { - impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount)); - } - } else { - impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, - mayPerformReboot(failedPackage))); - } - } - - Slog.i(TAG, "Checking available remediations for health check failure." - + " failedPackage: " - + (failedPackage == null ? null : failedPackage.getPackageName()) - + " failureReason: " + failureReason - + " available impact: " + impact); - return impact; - } - - @Override - public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage, - @FailureReasons int failureReason, int mitigationCount) { - if (isDisabled()) { - return MITIGATION_RESULT_SKIPPED; - } - Slog.i(TAG, "Executing remediation." - + " failedPackage: " - + (failedPackage == null ? null : failedPackage.getPackageName()) - + " failureReason: " + failureReason - + " mitigationCount: " + mitigationCount); - if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH - || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) { - final int level; - if (Flags.recoverabilityDetection()) { - if (!Flags.deprecateFlagsAndSettingsResets()) { - level = getRescueLevel(mitigationCount, mayPerformReboot(failedPackage), - failedPackage); - } else { - level = getRescueLevel(mitigationCount); - } - } else { - level = getRescueLevel(mitigationCount, mayPerformReboot(failedPackage)); - } - executeRescueLevel(mContext, - failedPackage == null ? null : failedPackage.getPackageName(), level); - return MITIGATION_RESULT_SUCCESS; - } else { - return MITIGATION_RESULT_SKIPPED; - } - } - - @Override - public boolean isPersistent() { - return true; - } - - @Override - public boolean mayObservePackage(String packageName) { - PackageManager pm = mContext.getPackageManager(); - try { - // A package is a module if this is non-null - if (pm.getModuleInfo(packageName, 0) != null) { - return true; - } - } catch (PackageManager.NameNotFoundException | IllegalStateException ignore) { - } - - return isPersistentSystemApp(packageName); - } - - @Override - public int onBootLoop(int mitigationCount) { - if (isDisabled()) { - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; - } - if (Flags.recoverabilityDetection()) { - if (!Flags.deprecateFlagsAndSettingsResets()) { - return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, - true, /*failedPackage=*/ null)); - } else { - return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount)); - } - } else { - return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true)); - } - } - - @Override - public int onExecuteBootLoopMitigation(int mitigationCount) { - if (isDisabled()) { - return MITIGATION_RESULT_SKIPPED; - } - boolean mayPerformReboot = !shouldThrottleReboot(); - final int level; - if (Flags.recoverabilityDetection()) { - if (!Flags.deprecateFlagsAndSettingsResets()) { - level = getRescueLevel(mitigationCount, mayPerformReboot, - /*failedPackage=*/ null); - } else { - level = getRescueLevel(mitigationCount); - } - } else { - level = getRescueLevel(mitigationCount, mayPerformReboot); - } - executeRescueLevel(mContext, /*failedPackage=*/ null, level); - return MITIGATION_RESULT_SUCCESS; - } - - @Override - public String getUniqueIdentifier() { - return NAME; - } - - /** - * Returns {@code true} if the failing package is non-null and performing a reboot or - * prompting a factory reset is an acceptable mitigation strategy for the package's - * failure, {@code false} otherwise. - */ - private boolean mayPerformReboot(@Nullable VersionedPackage failingPackage) { - if (failingPackage == null) { - return false; - } - if (shouldThrottleReboot()) { - return false; - } - - return isPersistentSystemApp(failingPackage.getPackageName()); - } - - private boolean isPersistentSystemApp(@NonNull String packageName) { - PackageManager pm = mContext.getPackageManager(); - try { - ApplicationInfo info = pm.getApplicationInfo(packageName, 0); - return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK; - } catch (PackageManager.NameNotFoundException e) { - return false; - } - } - - private synchronized Set<String> getCallingPackagesSet(String namespace) { - return mNamespaceCallingPackageSetMap.get(namespace); - } - } - - /** - * Returns {@code true} if Rescue Party is allowed to attempt a reboot or factory reset. - * Will return {@code false} if a factory reset was already offered recently. - */ - private static boolean shouldThrottleReboot() { - Long lastResetTime = getLastFactoryResetTimeMs(); - long now = System.currentTimeMillis(); - long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG, - DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN); - return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin); - } - - /** - * Hacky test to check if the device has an active USB connection, which is - * a good proxy for someone doing local development work. - */ - private static boolean isUsbActive() { - if (SystemProperties.getBoolean(PROP_VIRTUAL_DEVICE, false)) { - Slog.v(TAG, "Assuming virtual device is connected over USB"); - return true; - } - try { - final String state = FileUtils - .readTextFile(new File("/sys/class/android_usb/android0/state"), 128, ""); - return "CONFIGURED".equals(state.trim()); - } catch (Throwable t) { - Slog.w(TAG, "Failed to determine if device was on USB", t); - return false; - } - } - - private static class Level { - static int none() { - return Flags.recoverabilityDetection() ? RESCUE_LEVEL_NONE : LEVEL_NONE; - } - - static int reboot() { - return Flags.recoverabilityDetection() ? RESCUE_LEVEL_WARM_REBOOT : LEVEL_WARM_REBOOT; - } - - static int factoryReset() { - return Flags.recoverabilityDetection() - ? RESCUE_LEVEL_FACTORY_RESET - : LEVEL_FACTORY_RESET; - } - } - - private static String levelToString(int level) { - if (Flags.recoverabilityDetection()) { - switch (level) { - case RESCUE_LEVEL_NONE: - return "NONE"; - case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET: - return "SCOPED_DEVICE_CONFIG_RESET"; - case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET: - return "ALL_DEVICE_CONFIG_RESET"; - case RESCUE_LEVEL_WARM_REBOOT: - return "WARM_REBOOT"; - case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: - return "RESET_SETTINGS_UNTRUSTED_DEFAULTS"; - case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: - return "RESET_SETTINGS_UNTRUSTED_CHANGES"; - case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: - return "RESET_SETTINGS_TRUSTED_DEFAULTS"; - case RESCUE_LEVEL_FACTORY_RESET: - return "FACTORY_RESET"; - default: - return Integer.toString(level); - } - } else { - switch (level) { - case LEVEL_NONE: - return "NONE"; - case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: - return "RESET_SETTINGS_UNTRUSTED_DEFAULTS"; - case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: - return "RESET_SETTINGS_UNTRUSTED_CHANGES"; - case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: - return "RESET_SETTINGS_TRUSTED_DEFAULTS"; - case LEVEL_WARM_REBOOT: - return "WARM_REBOOT"; - case LEVEL_FACTORY_RESET: - return "FACTORY_RESET"; - default: - return Integer.toString(level); - } - } - } -} diff --git a/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryModule.java b/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryModule.java deleted file mode 100644 index 8a81aaa1e636..000000000000 --- a/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryModule.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.crashrecovery; - -import android.content.Context; - -import com.android.server.PackageWatchdog; -import com.android.server.RescueParty; -import com.android.server.SystemService; - - -/** This class encapsulate the lifecycle methods of CrashRecovery module. - * - * @hide - */ -public class CrashRecoveryModule { - private static final String TAG = "CrashRecoveryModule"; - - /** Lifecycle definition for CrashRecovery module. */ - public static class Lifecycle extends SystemService { - private Context mSystemContext; - private PackageWatchdog mPackageWatchdog; - - public Lifecycle(Context context) { - super(context); - mSystemContext = context; - mPackageWatchdog = PackageWatchdog.getInstance(context); - } - - @Override - public void onStart() { - RescueParty.registerHealthObserver(mSystemContext); - mPackageWatchdog.registerShutdownBroadcastReceiver(); - mPackageWatchdog.noteBoot(); - } - - @Override - public void onBootPhase(int phase) { - if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { - mPackageWatchdog.onPackagesReady(); - } - } - } -} diff --git a/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryUtils.java b/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryUtils.java deleted file mode 100644 index 2e2a93776f9d..000000000000 --- a/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryUtils.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.crashrecovery; - -import android.os.Environment; -import android.util.IndentingPrintWriter; -import android.util.Log; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.PrintWriter; -import java.time.LocalDateTime; -import java.time.ZoneId; - -/** - * Class containing helper methods for the CrashRecoveryModule. - * - * @hide - */ -public class CrashRecoveryUtils { - private static final String TAG = "CrashRecoveryUtils"; - private static final long MAX_CRITICAL_INFO_DUMP_SIZE = 1000 * 1000; // ~1MB - private static final Object sFileLock = new Object(); - - /** Persist recovery related events in crashrecovery events file.**/ - public static void logCrashRecoveryEvent(int priority, String msg) { - Log.println(priority, TAG, msg); - try { - File fname = getCrashRecoveryEventsFile(); - synchronized (sFileLock) { - FileOutputStream out = new FileOutputStream(fname, true); - PrintWriter pw = new PrintWriter(out); - String dateString = LocalDateTime.now(ZoneId.systemDefault()).toString(); - pw.println(dateString + ": " + msg); - pw.close(); - } - } catch (IOException e) { - Log.e(TAG, "Unable to log CrashRecoveryEvents " + e.getMessage()); - } - } - - /** Dump recovery related events from crashrecovery events file.**/ - public static void dumpCrashRecoveryEvents(IndentingPrintWriter pw) { - pw.println("CrashRecovery Events: "); - pw.increaseIndent(); - final File file = getCrashRecoveryEventsFile(); - final long skipSize = file.length() - MAX_CRITICAL_INFO_DUMP_SIZE; - synchronized (sFileLock) { - try (BufferedReader in = new BufferedReader(new FileReader(file))) { - if (skipSize > 0) { - in.skip(skipSize); - } - String line; - while ((line = in.readLine()) != null) { - pw.println(line); - } - } catch (IOException e) { - Log.e(TAG, "Unable to dump CrashRecoveryEvents " + e.getMessage()); - } - } - pw.decreaseIndent(); - } - - private static File getCrashRecoveryEventsFile() { - File systemDir = new File(Environment.getDataDirectory(), "system"); - return new File(systemDir, "crashrecovery-events.txt"); - } -} diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java deleted file mode 100644 index 4978df491c62..000000000000 --- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ /dev/null @@ -1,785 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.rollback; - -import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED; -import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS; -import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent; - -import android.annotation.AnyThread; -import android.annotation.FlaggedApi; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.annotation.WorkerThread; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.VersionedPackage; -import android.content.rollback.PackageRollbackInfo; -import android.content.rollback.RollbackInfo; -import android.content.rollback.RollbackManager; -import android.crashrecovery.flags.Flags; -import android.os.Environment; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.PowerManager; -import android.os.SystemProperties; -import android.sysprop.CrashRecoveryProperties; -import android.util.ArraySet; -import android.util.FileUtils; -import android.util.Log; -import android.util.Slog; -import android.util.SparseArray; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; -import com.android.server.PackageWatchdog; -import com.android.server.PackageWatchdog.FailureReasons; -import com.android.server.PackageWatchdog.PackageHealthObserver; -import com.android.server.PackageWatchdog.PackageHealthObserverImpact; -import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Set; -import java.util.function.Consumer; - -/** - * {@link PackageHealthObserver} for {@link RollbackManagerService}. - * This class monitors crashes and triggers RollbackManager rollback accordingly. - * It also monitors native crashes for some short while after boot. - * - * @hide - */ -@FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY) -@SuppressLint({"CallbackName"}) -@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) -public final class RollbackPackageHealthObserver implements PackageHealthObserver { - private static final String TAG = "RollbackPackageHealthObserver"; - private static final String NAME = "rollback-observer"; - private static final String CLASS_NAME = RollbackPackageHealthObserver.class.getName(); - - private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT - | ApplicationInfo.FLAG_SYSTEM; - - private static final String PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG = - "persist.device_config.configuration.disable_high_impact_rollback"; - - private final Context mContext; - private final Handler mHandler; - private final File mLastStagedRollbackIdsFile; - private final File mTwoPhaseRollbackEnabledFile; - // Staged rollback ids that have been committed but their session is not yet ready - private final Set<Integer> mPendingStagedRollbackIds = new ArraySet<>(); - // True if needing to roll back only rebootless apexes when native crash happens - private boolean mTwoPhaseRollbackEnabled; - - @VisibleForTesting - public RollbackPackageHealthObserver(@NonNull Context context) { - mContext = context; - HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver"); - handlerThread.start(); - mHandler = new Handler(handlerThread.getLooper()); - File dataDir = new File(Environment.getDataDirectory(), "rollback-observer"); - dataDir.mkdirs(); - mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids"); - mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled"); - PackageWatchdog.getInstance(mContext).registerHealthObserver(context.getMainExecutor(), - this); - - if (SystemProperties.getBoolean("sys.boot_completed", false)) { - // Load the value from the file if system server has crashed and restarted - mTwoPhaseRollbackEnabled = readBoolean(mTwoPhaseRollbackEnabledFile); - } else { - // Disable two-phase rollback for a normal reboot. We assume the rebootless apex - // installed before reboot is stable if native crash didn't happen. - mTwoPhaseRollbackEnabled = false; - writeBoolean(mTwoPhaseRollbackEnabledFile, false); - } - } - - @Override - public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage, - @FailureReasons int failureReason, int mitigationCount) { - int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; - if (Flags.recoverabilityDetection()) { - List<RollbackInfo> availableRollbacks = getAvailableRollbacks(); - List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel( - availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW); - if (!lowImpactRollbacks.isEmpty()) { - if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { - // For native crashes, we will directly roll back any available rollbacks at low - // impact level - impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; - } else if (getRollbackForPackage(failedPackage, lowImpactRollbacks) != null) { - // Rollback is available for crashing low impact package - impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; - } else { - impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70; - } - } - } else { - boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class) - .getAvailableRollbacks().isEmpty(); - - if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH - && anyRollbackAvailable) { - // For native crashes, we will directly roll back any available rollbacks - // Note: For non-native crashes the rollback-all step has higher impact - impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; - } else if (getAvailableRollback(failedPackage) != null) { - // Rollback is available, we may get a callback into #onExecuteHealthCheckMitigation - impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; - } else if (anyRollbackAvailable) { - // If any rollbacks are available, we will commit them - impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70; - } - } - - Slog.i(TAG, "Checking available remediations for health check failure." - + " failedPackage: " - + (failedPackage == null ? null : failedPackage.getPackageName()) - + " failureReason: " + failureReason - + " available impact: " + impact); - return impact; - } - - @Override - public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage, - @FailureReasons int rollbackReason, int mitigationCount) { - Slog.i(TAG, "Executing remediation." - + " failedPackage: " - + (failedPackage == null ? null : failedPackage.getPackageName()) - + " rollbackReason: " + rollbackReason - + " mitigationCount: " + mitigationCount); - if (Flags.recoverabilityDetection()) { - List<RollbackInfo> availableRollbacks = getAvailableRollbacks(); - if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { - mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason)); - return MITIGATION_RESULT_SUCCESS; - } - - List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel( - availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW); - RollbackInfo rollback = getRollbackForPackage(failedPackage, lowImpactRollbacks); - if (rollback != null) { - mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason)); - } else if (!lowImpactRollbacks.isEmpty()) { - // Apply all available low impact rollbacks. - mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason)); - } - } else { - if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { - mHandler.post(() -> rollbackAll(rollbackReason)); - return MITIGATION_RESULT_SUCCESS; - } - - RollbackInfo rollback = getAvailableRollback(failedPackage); - if (rollback != null) { - mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason)); - } else { - mHandler.post(() -> rollbackAll(rollbackReason)); - } - } - - // Assume rollbacks executed successfully - return MITIGATION_RESULT_SUCCESS; - } - - @Override - public int onBootLoop(int mitigationCount) { - int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; - if (Flags.recoverabilityDetection()) { - List<RollbackInfo> availableRollbacks = getAvailableRollbacks(); - if (!availableRollbacks.isEmpty()) { - impact = getUserImpactBasedOnRollbackImpactLevel(availableRollbacks); - } - } - return impact; - } - - @Override - public int onExecuteBootLoopMitigation(int mitigationCount) { - if (Flags.recoverabilityDetection()) { - List<RollbackInfo> availableRollbacks = getAvailableRollbacks(); - - triggerLeastImpactLevelRollback(availableRollbacks, - PackageWatchdog.FAILURE_REASON_BOOT_LOOP); - return MITIGATION_RESULT_SUCCESS; - } - return MITIGATION_RESULT_SKIPPED; - } - - @Override - @NonNull - public String getUniqueIdentifier() { - return NAME; - } - - @Override - public boolean isPersistent() { - return true; - } - - @Override - public boolean mayObservePackage(@NonNull String packageName) { - if (getAvailableRollbacks().isEmpty()) { - return false; - } - return isPersistentSystemApp(packageName); - } - - private List<RollbackInfo> getAvailableRollbacks() { - return mContext.getSystemService(RollbackManager.class).getAvailableRollbacks(); - } - - private boolean isPersistentSystemApp(@NonNull String packageName) { - PackageManager pm = mContext.getPackageManager(); - try { - ApplicationInfo info = pm.getApplicationInfo(packageName, 0); - return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK; - } catch (PackageManager.NameNotFoundException e) { - return false; - } - } - - private void assertInWorkerThread() { - Preconditions.checkState(mHandler.getLooper().isCurrentThread()); - } - - @AnyThread - @NonNull - public void notifyRollbackAvailable(@NonNull RollbackInfo rollback) { - mHandler.post(() -> { - // Enable two-phase rollback when a rebootless apex rollback is made available. - // We assume the rebootless apex is stable and is less likely to be the cause - // if native crash doesn't happen before reboot. So we will clear the flag and disable - // two-phase rollback after reboot. - if (isRebootlessApex(rollback)) { - mTwoPhaseRollbackEnabled = true; - writeBoolean(mTwoPhaseRollbackEnabledFile, true); - } - }); - } - - private static boolean isRebootlessApex(RollbackInfo rollback) { - if (!rollback.isStaged()) { - for (PackageRollbackInfo info : rollback.getPackages()) { - if (info.isApex()) { - return true; - } - } - } - return false; - } - - /** Verifies the rollback state after a reboot and schedules polling for sometime after reboot - * to check for native crashes and mitigate them if needed. - */ - @AnyThread - public void onBootCompletedAsync() { - mHandler.post(()->onBootCompleted()); - } - - @WorkerThread - private void onBootCompleted() { - assertInWorkerThread(); - - RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class); - if (!rollbackManager.getAvailableRollbacks().isEmpty()) { - // TODO(gavincorkery): Call into Package Watchdog from outside the observer - PackageWatchdog.getInstance(mContext).scheduleCheckAndMitigateNativeCrashes(); - } - - SparseArray<String> rollbackIds = popLastStagedRollbackIds(); - for (int i = 0; i < rollbackIds.size(); i++) { - WatchdogRollbackLogger.logRollbackStatusOnBoot(mContext, - rollbackIds.keyAt(i), rollbackIds.valueAt(i), - rollbackManager.getRecentlyCommittedRollbacks()); - } - } - - @AnyThread - private RollbackInfo getAvailableRollback(VersionedPackage failedPackage) { - RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class); - for (RollbackInfo rollback : rollbackManager.getAvailableRollbacks()) { - for (PackageRollbackInfo packageRollback : rollback.getPackages()) { - if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) { - return rollback; - } - // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have - // to rely on complicated reasoning as below - - // Due to b/147666157, for apk in apex, we do not know the version we are rolling - // back from. But if a package X is embedded in apex A exclusively (not embedded in - // any other apex), which is not guaranteed, then it is sufficient to check only - // package names here, as the version of failedPackage and the PackageRollbackInfo - // can't be different. If failedPackage has a higher version, then it must have - // been updated somehow. There are two ways: it was updated by an update of apex A - // or updated directly as apk. In both cases, this rollback would have gotten - // expired when onPackageReplaced() was called. Since the rollback exists, it has - // same version as failedPackage. - if (packageRollback.isApkInApex() - && packageRollback.getVersionRolledBackFrom().getPackageName() - .equals(failedPackage.getPackageName())) { - return rollback; - } - } - } - return null; - } - - @AnyThread - private RollbackInfo getRollbackForPackage(@Nullable VersionedPackage failedPackage, - List<RollbackInfo> availableRollbacks) { - if (failedPackage == null) { - return null; - } - - for (RollbackInfo rollback : availableRollbacks) { - for (PackageRollbackInfo packageRollback : rollback.getPackages()) { - if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) { - return rollback; - } - // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have - // to rely on complicated reasoning as below - - // Due to b/147666157, for apk in apex, we do not know the version we are rolling - // back from. But if a package X is embedded in apex A exclusively (not embedded in - // any other apex), which is not guaranteed, then it is sufficient to check only - // package names here, as the version of failedPackage and the PackageRollbackInfo - // can't be different. If failedPackage has a higher version, then it must have - // been updated somehow. There are two ways: it was updated by an update of apex A - // or updated directly as apk. In both cases, this rollback would have gotten - // expired when onPackageReplaced() was called. Since the rollback exists, it has - // same version as failedPackage. - if (packageRollback.isApkInApex() - && packageRollback.getVersionRolledBackFrom().getPackageName() - .equals(failedPackage.getPackageName())) { - return rollback; - } - } - } - return null; - } - - /** - * Returns {@code true} if staged session associated with {@code rollbackId} was marked - * as handled, {@code false} if already handled. - */ - @WorkerThread - private boolean markStagedSessionHandled(int rollbackId) { - assertInWorkerThread(); - return mPendingStagedRollbackIds.remove(rollbackId); - } - - /** - * Returns {@code true} if all pending staged rollback sessions were marked as handled, - * {@code false} if there is any left. - */ - @WorkerThread - private boolean isPendingStagedSessionsEmpty() { - assertInWorkerThread(); - return mPendingStagedRollbackIds.isEmpty(); - } - - private static boolean readBoolean(File file) { - try (FileInputStream fis = new FileInputStream(file)) { - return fis.read() == 1; - } catch (IOException ignore) { - return false; - } - } - - private static void writeBoolean(File file, boolean value) { - try (FileOutputStream fos = new FileOutputStream(file)) { - fos.write(value ? 1 : 0); - fos.flush(); - FileUtils.sync(fos); - } catch (IOException ignore) { - } - } - - @WorkerThread - private void saveStagedRollbackId(int stagedRollbackId, @Nullable VersionedPackage logPackage) { - assertInWorkerThread(); - writeStagedRollbackId(mLastStagedRollbackIdsFile, stagedRollbackId, logPackage); - } - - static void writeStagedRollbackId(File file, int stagedRollbackId, - @Nullable VersionedPackage logPackage) { - try { - FileOutputStream fos = new FileOutputStream(file, true); - PrintWriter pw = new PrintWriter(fos); - String logPackageName = logPackage != null ? logPackage.getPackageName() : ""; - pw.append(String.valueOf(stagedRollbackId)).append(",").append(logPackageName); - pw.println(); - pw.flush(); - FileUtils.sync(fos); - pw.close(); - } catch (IOException e) { - Slog.e(TAG, "Failed to save last staged rollback id", e); - file.delete(); - } - } - - @WorkerThread - private SparseArray<String> popLastStagedRollbackIds() { - assertInWorkerThread(); - try { - return readStagedRollbackIds(mLastStagedRollbackIdsFile); - } finally { - mLastStagedRollbackIdsFile.delete(); - } - } - - static SparseArray<String> readStagedRollbackIds(File file) { - SparseArray<String> result = new SparseArray<>(); - try { - String line; - BufferedReader reader = new BufferedReader(new FileReader(file)); - while ((line = reader.readLine()) != null) { - // Each line is of the format: "id,logging_package" - String[] values = line.trim().split(","); - String rollbackId = values[0]; - String logPackageName = ""; - if (values.length > 1) { - logPackageName = values[1]; - } - result.put(Integer.parseInt(rollbackId), logPackageName); - } - } catch (Exception ignore) { - return new SparseArray<>(); - } - return result; - } - - - /** - * Returns true if the package name is the name of a module. - */ - @AnyThread - private boolean isModule(String packageName) { - // Check if the package is listed among the system modules or is an - // APK inside an updatable APEX. - try { - PackageManager pm = mContext.getPackageManager(); - final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */); - String apexPackageName = pkg.getApexPackageName(); - if (apexPackageName != null) { - packageName = apexPackageName; - } - - return pm.getModuleInfo(packageName, 0 /* flags */) != null; - } catch (PackageManager.NameNotFoundException e) { - return false; - } - } - - /** - * Rolls back the session that owns {@code failedPackage} - * - * @param rollback {@code rollbackInfo} of the {@code failedPackage} - * @param failedPackage the package that needs to be rolled back - */ - @WorkerThread - private void rollbackPackage(RollbackInfo rollback, VersionedPackage failedPackage, - @FailureReasons int rollbackReason) { - assertInWorkerThread(); - String failedPackageName = (failedPackage == null ? null : failedPackage.getPackageName()); - - Slog.i(TAG, "Rolling back package. RollbackId: " + rollback.getRollbackId() - + " failedPackage: " + failedPackageName - + " rollbackReason: " + rollbackReason); - logCrashRecoveryEvent(Log.DEBUG, String.format("Rolling back %s. Reason: %s", - failedPackageName, rollbackReason)); - final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class); - int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason); - final String failedPackageToLog; - if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { - failedPackageToLog = SystemProperties.get( - "sys.init.updatable_crashing_process_name", ""); - } else { - failedPackageToLog = failedPackage.getPackageName(); - } - VersionedPackage logPackageTemp = null; - if (isModule(failedPackage.getPackageName())) { - logPackageTemp = WatchdogRollbackLogger.getLogPackage(mContext, failedPackage); - } - - final VersionedPackage logPackage = logPackageTemp; - WatchdogRollbackLogger.logEvent(logPackage, - CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE, - reasonToLog, failedPackageToLog); - - Consumer<Intent> onResult = result -> { - assertInWorkerThread(); - int status = result.getIntExtra(RollbackManager.EXTRA_STATUS, - RollbackManager.STATUS_FAILURE); - if (status == RollbackManager.STATUS_SUCCESS) { - if (rollback.isStaged()) { - int rollbackId = rollback.getRollbackId(); - saveStagedRollbackId(rollbackId, logPackage); - WatchdogRollbackLogger.logEvent(logPackage, - CrashRecoveryStatsLog - .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED, - reasonToLog, failedPackageToLog); - - } else { - WatchdogRollbackLogger.logEvent(logPackage, - CrashRecoveryStatsLog - .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS, - reasonToLog, failedPackageToLog); - } - } else { - WatchdogRollbackLogger.logEvent(logPackage, - CrashRecoveryStatsLog - .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE, - reasonToLog, failedPackageToLog); - } - if (rollback.isStaged()) { - markStagedSessionHandled(rollback.getRollbackId()); - // Wait for all pending staged sessions to get handled before rebooting. - if (isPendingStagedSessionsEmpty()) { - CrashRecoveryProperties.attemptingReboot(true); - mContext.getSystemService(PowerManager.class).reboot("Rollback staged install"); - } - } - }; - - // Define a BroadcastReceiver to handle the result - BroadcastReceiver rollbackReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent result) { - mHandler.post(() -> onResult.accept(result)); - } - }; - - String intentActionName = CLASS_NAME + rollback.getRollbackId(); - // Register the BroadcastReceiver - mContext.registerReceiver(rollbackReceiver, - new IntentFilter(intentActionName), - Context.RECEIVER_NOT_EXPORTED); - - Intent intentReceiver = new Intent(intentActionName); - intentReceiver.putExtra("rollbackId", rollback.getRollbackId()); - intentReceiver.setPackage(mContext.getPackageName()); - intentReceiver.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - - PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext, - rollback.getRollbackId(), - intentReceiver, - PendingIntent.FLAG_MUTABLE); - - rollbackManager.commitRollback(rollback.getRollbackId(), - Collections.singletonList(failedPackage), - rollbackPendingIntent.getIntentSender()); - } - - /** - * Two-phase rollback: - * 1. roll back rebootless apexes first - * 2. roll back all remaining rollbacks if native crash doesn't stop after (1) is done - * - * This approach gives us a better chance to correctly attribute native crash to rebootless - * apex update without rolling back Mainline updates which might contains critical security - * fixes. - */ - @WorkerThread - private boolean useTwoPhaseRollback(List<RollbackInfo> rollbacks) { - assertInWorkerThread(); - if (!mTwoPhaseRollbackEnabled) { - return false; - } - - Slog.i(TAG, "Rolling back all rebootless APEX rollbacks"); - boolean found = false; - for (RollbackInfo rollback : rollbacks) { - if (isRebootlessApex(rollback)) { - VersionedPackage firstRollback = - rollback.getPackages().get(0).getVersionRolledBackFrom(); - rollbackPackage(rollback, firstRollback, - PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); - found = true; - } - } - return found; - } - - /** - * Rollback the package that has minimum rollback impact level. - * @param availableRollbacks all available rollbacks - * @param rollbackReason reason to rollback - */ - private void triggerLeastImpactLevelRollback(List<RollbackInfo> availableRollbacks, - @FailureReasons int rollbackReason) { - int minRollbackImpactLevel = getMinRollbackImpactLevel(availableRollbacks); - - if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_LOW) { - // Apply all available low impact rollbacks. - mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason)); - } else if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH) { - // Check disable_high_impact_rollback device config before performing rollback - if (SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) { - return; - } - // Rollback one package at a time. If that doesn't resolve the issue, rollback - // next with same impact level. - mHandler.post(() -> rollbackHighImpact(availableRollbacks, rollbackReason)); - } - } - - /** - * sort the available high impact rollbacks by first package name to have a deterministic order. - * Apply the first available rollback. - * @param availableRollbacks all available rollbacks - * @param rollbackReason reason to rollback - */ - @WorkerThread - private void rollbackHighImpact(List<RollbackInfo> availableRollbacks, - @FailureReasons int rollbackReason) { - assertInWorkerThread(); - List<RollbackInfo> highImpactRollbacks = - getRollbacksAvailableForImpactLevel( - availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_HIGH); - - // sort rollbacks based on package name of the first package. This is to have a - // deterministic order of rollbacks. - List<RollbackInfo> sortedHighImpactRollbacks = highImpactRollbacks.stream().sorted( - Comparator.comparing(a -> a.getPackages().get(0).getPackageName())).toList(); - VersionedPackage firstRollback = - sortedHighImpactRollbacks - .get(0) - .getPackages() - .get(0) - .getVersionRolledBackFrom(); - Slog.i(TAG, "Rolling back high impact rollback for package: " - + firstRollback.getPackageName()); - rollbackPackage(sortedHighImpactRollbacks.get(0), firstRollback, rollbackReason); - } - - @WorkerThread - private void rollbackAll(@FailureReasons int rollbackReason) { - assertInWorkerThread(); - RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class); - List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks(); - if (useTwoPhaseRollback(rollbacks)) { - return; - } - - Slog.i(TAG, "Rolling back all available rollbacks"); - // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all - // pending staged rollbacks are handled. - for (RollbackInfo rollback : rollbacks) { - if (rollback.isStaged()) { - mPendingStagedRollbackIds.add(rollback.getRollbackId()); - } - } - - for (RollbackInfo rollback : rollbacks) { - VersionedPackage firstRollback = - rollback.getPackages().get(0).getVersionRolledBackFrom(); - rollbackPackage(rollback, firstRollback, rollbackReason); - } - } - - /** - * Rollback all available low impact rollbacks - * @param availableRollbacks all available rollbacks - * @param rollbackReason reason to rollbacks - */ - @WorkerThread - private void rollbackAllLowImpact( - List<RollbackInfo> availableRollbacks, @FailureReasons int rollbackReason) { - assertInWorkerThread(); - - List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel( - availableRollbacks, - PackageManager.ROLLBACK_USER_IMPACT_LOW); - if (useTwoPhaseRollback(lowImpactRollbacks)) { - return; - } - - Slog.i(TAG, "Rolling back all available low impact rollbacks"); - logCrashRecoveryEvent(Log.DEBUG, "Rolling back all available. Reason: " + rollbackReason); - // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all - // pending staged rollbacks are handled. - for (RollbackInfo rollback : lowImpactRollbacks) { - if (rollback.isStaged()) { - mPendingStagedRollbackIds.add(rollback.getRollbackId()); - } - } - - for (RollbackInfo rollback : lowImpactRollbacks) { - VersionedPackage firstRollback = - rollback.getPackages().get(0).getVersionRolledBackFrom(); - rollbackPackage(rollback, firstRollback, rollbackReason); - } - } - - private List<RollbackInfo> getRollbacksAvailableForImpactLevel( - List<RollbackInfo> availableRollbacks, int impactLevel) { - return availableRollbacks.stream() - .filter(rollbackInfo -> rollbackInfo.getRollbackImpactLevel() == impactLevel) - .toList(); - } - - private int getMinRollbackImpactLevel(List<RollbackInfo> availableRollbacks) { - return availableRollbacks.stream() - .mapToInt(RollbackInfo::getRollbackImpactLevel) - .min() - .orElse(-1); - } - - private int getUserImpactBasedOnRollbackImpactLevel(List<RollbackInfo> availableRollbacks) { - int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; - int minImpact = getMinRollbackImpactLevel(availableRollbacks); - switch (minImpact) { - case PackageManager.ROLLBACK_USER_IMPACT_LOW: - impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70; - break; - case PackageManager.ROLLBACK_USER_IMPACT_HIGH: - if (!SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) { - impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_90; - } - break; - default: - impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; - } - return impact; - } - - @VisibleForTesting - Handler getHandler() { - return mHandler; - } -} diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/WatchdogRollbackLogger.java deleted file mode 100644 index 9cfed02f9355..000000000000 --- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/WatchdogRollbackLogger.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.rollback; - -import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent; -import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH; -import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING; -import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING; -import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK; -import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH; -import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT; -import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN; -import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED; -import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE; -import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE; -import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInstaller; -import android.content.pm.PackageManager; -import android.content.pm.VersionedPackage; -import android.content.rollback.PackageRollbackInfo; -import android.content.rollback.RollbackInfo; -import android.os.SystemProperties; -import android.text.TextUtils; -import android.util.Log; -import android.util.Slog; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.PackageWatchdog; -import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog; - -import java.util.List; - -/** - * This class handles the logic for logging Watchdog-triggered rollback events. - * @hide - */ -public final class WatchdogRollbackLogger { - private static final String TAG = "WatchdogRollbackLogger"; - - private static final String LOGGING_PARENT_KEY = "android.content.pm.LOGGING_PARENT"; - - private WatchdogRollbackLogger() { - } - - @Nullable - private static String getLoggingParentName(Context context, @NonNull String packageName) { - PackageManager packageManager = context.getPackageManager(); - try { - int flags = PackageManager.MATCH_APEX | PackageManager.GET_META_DATA; - ApplicationInfo ai = packageManager.getPackageInfo(packageName, flags).applicationInfo; - if (ai.metaData == null) { - return null; - } - return ai.metaData.getString(LOGGING_PARENT_KEY); - } catch (Exception e) { - Slog.w(TAG, "Unable to discover logging parent package: " + packageName, e); - return null; - } - } - - /** - * Returns the logging parent of a given package if it exists, {@code null} otherwise. - * - * The logging parent is defined by the {@code android.content.pm.LOGGING_PARENT} field in the - * metadata of a package's AndroidManifest.xml. - */ - @VisibleForTesting - @Nullable - static VersionedPackage getLogPackage(Context context, - @NonNull VersionedPackage failingPackage) { - String logPackageName; - VersionedPackage loggingParent; - logPackageName = getLoggingParentName(context, failingPackage.getPackageName()); - if (logPackageName == null) { - return null; - } - try { - loggingParent = new VersionedPackage(logPackageName, context.getPackageManager() - .getPackageInfo(logPackageName, 0 /* flags */).getLongVersionCode()); - } catch (PackageManager.NameNotFoundException e) { - return null; - } - return loggingParent; - } - - static void logRollbackStatusOnBoot(Context context, int rollbackId, String logPackageName, - List<RollbackInfo> recentlyCommittedRollbacks) { - PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller(); - - RollbackInfo rollback = null; - for (RollbackInfo info : recentlyCommittedRollbacks) { - if (rollbackId == info.getRollbackId()) { - rollback = info; - break; - } - } - - if (rollback == null) { - Slog.e(TAG, "rollback info not found for last staged rollback: " + rollbackId); - return; - } - - // Use the version of the logging parent that was installed before - // we rolled back for logging purposes. - VersionedPackage oldLoggingPackage = null; - if (!TextUtils.isEmpty(logPackageName)) { - for (PackageRollbackInfo packageRollback : rollback.getPackages()) { - if (logPackageName.equals(packageRollback.getPackageName())) { - oldLoggingPackage = packageRollback.getVersionRolledBackFrom(); - break; - } - } - } - - int sessionId = rollback.getCommittedSessionId(); - PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId); - if (sessionInfo == null) { - Slog.e(TAG, "On boot completed, could not load session id " + sessionId); - return; - } - - if (sessionInfo.isStagedSessionApplied()) { - logEvent(oldLoggingPackage, - WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS, - WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, ""); - } else if (sessionInfo.isStagedSessionFailed()) { - logEvent(oldLoggingPackage, - WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE, - WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, ""); - } - } - - /** - * Log a Watchdog rollback event to statsd. - * - * @param logPackage the package to associate the rollback with. - * @param type the state of the rollback. - * @param rollbackReason the reason Watchdog triggered a rollback, if known. - * @param failingPackageName the failing package or process which triggered the rollback. - */ - public static void logEvent(@Nullable VersionedPackage logPackage, int type, - int rollbackReason, @NonNull String failingPackageName) { - String logMsg = "Watchdog event occurred with type: " + rollbackTypeToString(type) - + " logPackage: " + logPackage - + " rollbackReason: " + rollbackReasonToString(rollbackReason) - + " failedPackageName: " + failingPackageName; - Slog.i(TAG, logMsg); - if (logPackage != null) { - CrashRecoveryStatsLog.write( - CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED, - type, - logPackage.getPackageName(), - logPackage.getVersionCode(), - rollbackReason, - failingPackageName, - new byte[]{}); - } else { - // In the case that the log package is null, still log an empty string as an - // indication that retrieving the logging parent failed. - CrashRecoveryStatsLog.write( - CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED, - type, - "", - 0, - rollbackReason, - failingPackageName, - new byte[]{}); - } - - logTestProperties(logMsg); - } - - /** - * Writes properties which will be used by rollback tests to check if particular rollback - * events have occurred. - */ - private static void logTestProperties(String logMsg) { - // This property should be on only during the tests - if (!SystemProperties.getBoolean("persist.sys.rollbacktest.enabled", false)) { - return; - } - logCrashRecoveryEvent(Log.DEBUG, logMsg); - } - - @VisibleForTesting - static int mapFailureReasonToMetric(@PackageWatchdog.FailureReasons int failureReason) { - switch (failureReason) { - case PackageWatchdog.FAILURE_REASON_NATIVE_CRASH: - return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH; - case PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK: - return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK; - case PackageWatchdog.FAILURE_REASON_APP_CRASH: - return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH; - case PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING: - return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING; - case PackageWatchdog.FAILURE_REASON_BOOT_LOOP: - return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING; - default: - return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN; - } - } - - private static String rollbackTypeToString(int type) { - switch (type) { - case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE: - return "ROLLBACK_INITIATE"; - case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS: - return "ROLLBACK_SUCCESS"; - case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE: - return "ROLLBACK_FAILURE"; - case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED: - return "ROLLBACK_BOOT_TRIGGERED"; - default: - return "UNKNOWN"; - } - } - - private static String rollbackReasonToString(int reason) { - switch (reason) { - case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH: - return "REASON_NATIVE_CRASH"; - case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK: - return "REASON_EXPLICIT_HEALTH_CHECK"; - case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH: - return "REASON_APP_CRASH"; - case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING: - return "REASON_APP_NOT_RESPONDING"; - case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT: - return "REASON_NATIVE_CRASH_DURING_BOOT"; - case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING: - return "REASON_BOOT_LOOP"; - default: - return "UNKNOWN"; - } - } -} diff --git a/packages/CrashRecovery/services/module/java/com/android/util/ArrayUtils.java b/packages/CrashRecovery/services/module/java/com/android/util/ArrayUtils.java deleted file mode 100644 index 29ff7cced897..000000000000 --- a/packages/CrashRecovery/services/module/java/com/android/util/ArrayUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2024 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.util; - -import android.annotation.Nullable; - -/** - * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java - * - * @hide - */ -public class ArrayUtils { - private ArrayUtils() { /* cannot be instantiated */ } - - /** - * Checks if given array is null or has zero elements. - */ - public static boolean isEmpty(@Nullable int[] array) { - return array == null || array.length == 0; - } - - /** - * True if the byte array is null or has length 0. - */ - public static boolean isEmpty(@Nullable byte[] array) { - return array == null || array.length == 0; - } -} diff --git a/packages/CrashRecovery/services/module/java/com/android/util/FileUtils.java b/packages/CrashRecovery/services/module/java/com/android/util/FileUtils.java deleted file mode 100644 index d60a9b9847ca..000000000000 --- a/packages/CrashRecovery/services/module/java/com/android/util/FileUtils.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2024 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.util; - -import android.annotation.Nullable; - -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Bits and pieces copied from hidden API of android.os.FileUtils. - * - * @hide - */ -public class FileUtils { - /** - * Read a text file into a String, optionally limiting the length. - * - * @param file to read (will not seek, so things like /proc files are OK) - * @param max length (positive for head, negative of tail, 0 for no limit) - * @param ellipsis to add of the file was truncated (can be null) - * @return the contents of the file, possibly truncated - * @throws IOException if something goes wrong reading the file - * @hide - */ - public static @Nullable String readTextFile(@Nullable File file, @Nullable int max, - @Nullable String ellipsis) throws IOException { - InputStream input = new FileInputStream(file); - // wrapping a BufferedInputStream around it because when reading /proc with unbuffered - // input stream, bytes read not equal to buffer size is not necessarily the correct - // indication for EOF; but it is true for BufferedInputStream due to its implementation. - BufferedInputStream bis = new BufferedInputStream(input); - try { - long size = file.length(); - if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes - if (size > 0 && (max == 0 || size < max)) max = (int) size; - byte[] data = new byte[max + 1]; - int length = bis.read(data); - if (length <= 0) return ""; - if (length <= max) return new String(data, 0, length); - if (ellipsis == null) return new String(data, 0, max); - return new String(data, 0, max) + ellipsis; - } else if (max < 0) { // "tail" mode: keep the last N - int len; - boolean rolled = false; - byte[] last = null; - byte[] data = null; - do { - if (last != null) rolled = true; - byte[] tmp = last; - last = data; - data = tmp; - if (data == null) data = new byte[-max]; - len = bis.read(data); - } while (len == data.length); - - if (last == null && len <= 0) return ""; - if (last == null) return new String(data, 0, len); - if (len > 0) { - rolled = true; - System.arraycopy(last, len, last, 0, last.length - len); - System.arraycopy(data, 0, last, last.length - len, len); - } - if (ellipsis == null || !rolled) return new String(last); - return ellipsis + new String(last); - } else { // "cat" mode: size unknown, read it all in streaming fashion - ByteArrayOutputStream contents = new ByteArrayOutputStream(); - int len; - byte[] data = new byte[1024]; - do { - len = bis.read(data); - if (len > 0) contents.write(data, 0, len); - } while (len == data.length); - return contents.toString(); - } - } finally { - bis.close(); - input.close(); - } - } - - /** - * Perform an fsync on the given FileOutputStream. The stream at this - * point must be flushed but not yet closed. - * - * @hide - */ - public static boolean sync(FileOutputStream stream) { - try { - if (stream != null) { - stream.getFD().sync(); - } - return true; - } catch (IOException e) { - } - return false; - } -} diff --git a/packages/CrashRecovery/services/module/java/com/android/util/LongArrayQueue.java b/packages/CrashRecovery/services/module/java/com/android/util/LongArrayQueue.java deleted file mode 100644 index 9a24ada8b69a..000000000000 --- a/packages/CrashRecovery/services/module/java/com/android/util/LongArrayQueue.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2024 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.util; - -import libcore.util.EmptyArray; - -import java.util.NoSuchElementException; - -/** - * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java - * - * @hide - */ -public class LongArrayQueue { - - private long[] mValues; - private int mSize; - private int mHead; - private int mTail; - - private long[] newUnpaddedLongArray(int num) { - return new long[num]; - } - /** - * Initializes a queue with the given starting capacity. - * - * @param initialCapacity the capacity. - */ - public LongArrayQueue(int initialCapacity) { - if (initialCapacity == 0) { - mValues = EmptyArray.LONG; - } else { - mValues = newUnpaddedLongArray(initialCapacity); - } - mSize = 0; - mHead = mTail = 0; - } - - /** - * Initializes a queue with default starting capacity. - */ - public LongArrayQueue() { - this(16); - } - - /** @hide */ - public static int growSize(int currentSize) { - return currentSize <= 4 ? 8 : currentSize * 2; - } - - private void grow() { - if (mSize < mValues.length) { - throw new IllegalStateException("Queue not full yet!"); - } - final int newSize = growSize(mSize); - final long[] newArray = newUnpaddedLongArray(newSize); - final int r = mValues.length - mHead; // Number of elements on and to the right of head. - System.arraycopy(mValues, mHead, newArray, 0, r); - System.arraycopy(mValues, 0, newArray, r, mHead); - mValues = newArray; - mHead = 0; - mTail = mSize; - } - - /** - * Returns the number of elements in the queue. - */ - public int size() { - return mSize; - } - - /** - * Removes all elements from this queue. - */ - public void clear() { - mSize = 0; - mHead = mTail = 0; - } - - /** - * Adds a value to the tail of the queue. - * - * @param value the value to be added. - */ - public void addLast(long value) { - if (mSize == mValues.length) { - grow(); - } - mValues[mTail] = value; - mTail = (mTail + 1) % mValues.length; - mSize++; - } - - /** - * Removes an element from the head of the queue. - * - * @return the element at the head of the queue. - * @throws NoSuchElementException if the queue is empty. - */ - public long removeFirst() { - if (mSize == 0) { - throw new NoSuchElementException("Queue is empty!"); - } - final long ret = mValues[mHead]; - mHead = (mHead + 1) % mValues.length; - mSize--; - return ret; - } - - /** - * Returns the element at the given position from the head of the queue, where 0 represents the - * head of the queue. - * - * @param position the position from the head of the queue. - * @return the element found at the given position. - * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or - * {@code position} >= {@link #size()} - */ - public long get(int position) { - if (position < 0 || position >= mSize) { - throw new IndexOutOfBoundsException("Index " + position - + " not valid for a queue of size " + mSize); - } - final int index = (mHead + position) % mValues.length; - return mValues[index]; - } - - /** - * Returns the element at the head of the queue, without removing it. - * - * @return the element at the head of the queue. - * @throws NoSuchElementException if the queue is empty - */ - public long peekFirst() { - if (mSize == 0) { - throw new NoSuchElementException("Queue is empty!"); - } - return mValues[mHead]; - } - - /** - * Returns the element at the tail of the queue. - * - * @return the element at the tail of the queue. - * @throws NoSuchElementException if the queue is empty. - */ - public long peekLast() { - if (mSize == 0) { - throw new NoSuchElementException("Queue is empty!"); - } - final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1; - return mValues[index]; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - if (mSize <= 0) { - return "{}"; - } - - final StringBuilder buffer = new StringBuilder(mSize * 64); - buffer.append('{'); - buffer.append(get(0)); - for (int i = 1; i < mSize; i++) { - buffer.append(", "); - buffer.append(get(i)); - } - buffer.append('}'); - return buffer.toString(); - } -} diff --git a/packages/CrashRecovery/services/module/java/com/android/util/XmlUtils.java b/packages/CrashRecovery/services/module/java/com/android/util/XmlUtils.java deleted file mode 100644 index 488b531c2b8a..000000000000 --- a/packages/CrashRecovery/services/module/java/com/android/util/XmlUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2024 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.util; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; - -/** - * Bits and pieces copied from hidden API of - * frameworks/base/core/java/com/android/internal/util/XmlUtils.java - * - * @hide - */ -public class XmlUtils { - - /** @hide */ - public static final void beginDocument(XmlPullParser parser, String firstElementName) - throws XmlPullParserException, IOException { - int type; - while ((type = parser.next()) != parser.START_TAG - && type != parser.END_DOCUMENT) { - // Do nothing - } - - if (type != parser.START_TAG) { - throw new XmlPullParserException("No start tag found"); - } - - if (!parser.getName().equals(firstElementName)) { - throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() - + ", expected " + firstElementName); - } - } - - /** @hide */ - public static boolean nextElementWithin(XmlPullParser parser, int outerDepth) - throws IOException, XmlPullParserException { - for (;;) { - int type = parser.next(); - if (type == XmlPullParser.END_DOCUMENT - || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) { - return false; - } - if (type == XmlPullParser.START_TAG - && parser.getDepth() == outerDepth + 1) { - return true; - } - } - } -} diff --git a/packages/EasterEgg/AndroidManifest.xml b/packages/EasterEgg/AndroidManifest.xml index 754abb2f76be..96e5892f4d1d 100644 --- a/packages/EasterEgg/AndroidManifest.xml +++ b/packages/EasterEgg/AndroidManifest.xml @@ -33,7 +33,7 @@ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <application - android:icon="@drawable/android15_patch_adaptive" + android:icon="@drawable/android16_patch_adaptive" android:label="@string/app_name"> <!-- Android V easter egg: Daydream version of Landroid @@ -41,7 +41,7 @@ <service android:name=".landroid.DreamUniverse" android:exported="true" - android:icon="@drawable/android15_patch_adaptive" + android:icon="@drawable/android16_patch_adaptive" android:label="@string/v_egg_name" android:description="@string/dream_description" android:enabled="false" @@ -62,7 +62,7 @@ android:name=".landroid.MainActivity" android:exported="true" android:label="@string/u_egg_name" - android:icon="@drawable/android15_patch_adaptive" + android:icon="@drawable/android16_patch_adaptive" android:configChanges="orientation|screenLayout|screenSize|density" android:theme="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"> <intent-filter> diff --git a/packages/EasterEgg/res/drawable/android16_patch_adaptive.xml b/packages/EasterEgg/res/drawable/android16_patch_adaptive.xml new file mode 100644 index 000000000000..277df47438e3 --- /dev/null +++ b/packages/EasterEgg/res/drawable/android16_patch_adaptive.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2024 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. +--> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/android16_patch_adaptive_background"/> + <foreground android:drawable="@drawable/android16_patch_adaptive_foreground"/> + <monochrome android:drawable="@drawable/android16_patch_monochrome"/> +</adaptive-icon> diff --git a/packages/EasterEgg/res/drawable/android16_patch_adaptive_background.xml b/packages/EasterEgg/res/drawable/android16_patch_adaptive_background.xml new file mode 100644 index 000000000000..17c2b927f4fd --- /dev/null +++ b/packages/EasterEgg/res/drawable/android16_patch_adaptive_background.xml @@ -0,0 +1,245 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2024 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <group> + <clip-path + android:pathData="M0,0h108v108h-108z"/> + <path + android:pathData="M73,54L54,35L35,54L54,73L73,54Z" + android:fillColor="#34A853"/> + <path + android:pathData="M44.5,44.5L54,44.5L44.5,54L44.5,44.5Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M63.5,63.5L54,63.5L63.5,54L63.5,63.5Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M54,54L54,44.5L63.5,54L54,54Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M54,44.5L54,35L63.5,44.5L54,44.5Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M54,63.5L54,73L44.5,63.5L54,63.5Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M63.5,54L63.5,44.5L73,54L63.5,54Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M44.5,54L44.5,63.5L35,54L44.5,54Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M54,54L54,63.5L44.5,54L54,54Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M82.5,25.5L82.5,35L73,25.5L82.5,25.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,44.5L63.5,35L73,44.5L63.5,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,35L82.5,35L73,44.5L73,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M82.5,35L92,35L82.5,44.5L82.5,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,35L54,35L63.5,25.5L63.5,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,44.5L82.5,44.5L73,54L73,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,25.5L63.5,25.5L73,16L73,25.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,35L63.5,35L73,25.5L73,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M82.5,63.5L82.5,73L73,63.5L82.5,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,82.5L63.5,73L73,82.5L63.5,82.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,73L82.5,73L73,82.5L73,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M82.5,73L92,73L82.5,82.5L82.5,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,73L54,73L63.5,63.5L63.5,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,82.5L82.5,82.5L73,92L73,82.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,63.5L63.5,63.5L73,54L73,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,73L63.5,73L73,63.5L73,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,63.5L44.5,73L35,63.5L44.5,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M25.5,82.5L25.5,73L35,82.5L25.5,82.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,73L44.5,73L35,82.5L35,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,73L54,73L44.5,82.5L44.5,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M25.5,73L16,73L25.5,63.5L25.5,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,82.5L44.5,82.5L35,92L35,82.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,63.5L25.5,63.5L35,54L35,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,73L25.5,73L35,63.5L35,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,25.5L44.5,35L35,25.5L44.5,25.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M25.5,44.5L25.5,35L35,44.5L25.5,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,35L44.5,35L35,44.5L35,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,35L54,35L44.5,44.5L44.5,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M25.5,35L16,35L25.5,25.5L25.5,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,44.5L44.5,44.5L35,54L35,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,25.5L25.5,25.5L35,16L35,25.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,35L25.5,35L35,25.5L35,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,25.5L54,25.5L63.5,16L63.5,25.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,6.5L54,6.5L44.5,16L44.5,6.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,16L54,25.5L44.5,16L54,16Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,25.5L54,35L44.5,25.5L54,25.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,6.5L54,-3L63.5,6.5L54,6.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,16L44.5,25.5L35,16L44.5,16Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,16L63.5,6.5L73,16L63.5,16Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,16L54,6.5L63.5,16L54,16Z" + android:fillColor="#16161D"/> + <path + android:pathData="M101.5,63.5L92,63.5L101.5,54L101.5,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M82.5,44.5L92,44.5L82.5,54L82.5,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M92,54L92,63.5L82.5,54L92,54Z" + android:fillColor="#16161D"/> + <path + android:pathData="M92,63.5L92,73L82.5,63.5L92,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M92,44.5L92,35L101.5,44.5L92,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M82.5,54L82.5,63.5L73,54L82.5,54Z" + android:fillColor="#16161D"/> + <path + android:pathData="M101.5,54L101.5,44.5L111,54L101.5,54Z" + android:fillColor="#16161D"/> + <path + android:pathData="M92,54L92,44.5L101.5,54L92,54Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,101.5L54,101.5L63.5,92L63.5,101.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,82.5L54,82.5L44.5,92L44.5,82.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,92L54,101.5L44.5,92L54,92Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,101.5L54,111L44.5,101.5L54,101.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,82.5L54,73L63.5,82.5L54,82.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,92L44.5,101.5L35,92L44.5,92Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,92L63.5,82.5L73,92L63.5,92Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,92L54,82.5L63.5,92L54,92Z" + android:fillColor="#16161D"/> + <path + android:pathData="M25.5,63.5L16,63.5L25.5,54L25.5,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M6.5,44.5L16,44.5L6.5,54L6.5,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M16,54L16,63.5L6.5,54L16,54Z" + android:fillColor="#16161D"/> + <path + android:pathData="M16,63.5L16,73L6.5,63.5L16,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M16,44.5L16,35L25.5,44.5L16,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M6.5,54L6.5,63.5L-3,54L6.5,54Z" + android:fillColor="#16161D"/> + <path + android:pathData="M25.5,54L25.5,44.5L35,54L25.5,54Z" + android:fillColor="#16161D"/> + <path + android:pathData="M16,54L16,44.5L25.5,54L16,54Z" + android:fillColor="#16161D"/> + </group> +</vector> diff --git a/packages/EasterEgg/res/drawable/android16_patch_adaptive_foreground.xml b/packages/EasterEgg/res/drawable/android16_patch_adaptive_foreground.xml new file mode 100644 index 000000000000..4c2932399c1a --- /dev/null +++ b/packages/EasterEgg/res/drawable/android16_patch_adaptive_foreground.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2024 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path + android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z" + android:fillColor="#C6FF00" + android:fillType="evenOdd"/> + <path + android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z" + android:fillColor="#000000"/> + <path + android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.973 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z" + android:fillColor="#000000"/> + <path + android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.557C62.817,58.796 63.205,58.796 63.444,58.557L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z" + android:fillColor="#000000"/> +</vector> diff --git a/packages/EasterEgg/res/drawable/android16_patch_monochrome.xml b/packages/EasterEgg/res/drawable/android16_patch_monochrome.xml new file mode 100644 index 000000000000..608d5ea6ee48 --- /dev/null +++ b/packages/EasterEgg/res/drawable/android16_patch_monochrome.xml @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2024 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path + android:strokeWidth="1" + android:pathData="M54.707,35.707L72.293,53.293A1,1 102.155,0 1,72.293 54.707L54.707,72.293A1,1 0,0 1,53.293 72.293L35.707,54.707A1,1 0,0 1,35.707 53.293L53.293,35.707A1,1 0,0 1,54.707 35.707z" + android:fillColor="#00000000" + android:strokeColor="#ffffff"/> + <path + android:pathData="M55.237,35.177L72.823,52.763A1.75,1.75 67.835,0 1,72.823 55.237L55.237,72.823A1.75,1.75 77.684,0 1,52.763 72.823L35.177,55.237A1.75,1.75 0,0 1,35.177 52.763L52.763,35.177A1.75,1.75 0,0 1,55.237 35.177z" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#ffffff"/> + <path + android:pathData="M44.5,44.5h19v19h-19z" + android:strokeWidth="0.75" + android:fillColor="#00000000" + android:strokeColor="#ffffff"/> + <path + android:pathData="M54,44.5l9.5,9.5l-9.5,9.5l-9.5,-9.5z" + android:strokeWidth="0.75" + android:fillColor="#00000000" + android:strokeColor="#ffffff"/> + <path + android:pathData="M54,35V73" + android:strokeWidth="0.75" + android:fillColor="#00000000" + android:strokeColor="#ffffff"/> + <path + android:pathData="M73,54L35,54" + android:strokeWidth="0.75" + android:fillColor="#00000000" + android:strokeColor="#ffffff"/> + <path + android:pathData="M33.576,31.135l1.146,1.146l-1.146,1.146l-1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M31.146,65.966l1.146,1.146l-1.146,1.146l-1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M26.718,56l1.718,1.718l-1.718,1.718l-1.718,-1.718z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M31.146,48l1.146,1.146l-1.146,1.146l-1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M41.925,34.374l1.718,1.718l-1.718,1.718l-1.718,-1.718z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M63.146,71l1.146,1.146l-1.146,1.146l-1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M48.567,74.553l1.718,1.718l-1.718,1.718l-1.718,-1.718z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M51.146,26l1.146,1.146l-1.146,1.146l-1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M72.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M76.531,36.417l-1.718,1.718l-1.718,-1.718l1.718,-1.718z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M58.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M68.419,36.978l-1.146,1.146l-1.146,-1.146l1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M74.252,64.034l-1.146,1.146l-1.146,-1.146l1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M71.437,76.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M42.984,69.38l-1.146,1.146l-1.146,-1.146l1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M82.437,51.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z" + android:fillColor="#ffffff"/> + <path + android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.972 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z" + android:fillColor="#ffffff"/> + <path + android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.556C62.817,58.796 63.205,58.796 63.444,58.556L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z" + android:fillColor="#ffffff"/> +</vector> diff --git a/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt b/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt index 8c87c5d4af7b..d56e8b9e8d0e 100644 --- a/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt +++ b/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt @@ -59,7 +59,7 @@ class DreamUniverse : DreamService() { override fun onAttachedToWindow() { super.onAttachedToWindow() - val universe = VisibleUniverse(namer = Namer(resources), randomSeed = randomSeed()) + val universe = Universe(namer = Namer(resources), randomSeed = randomSeed()) isInteractive = false diff --git a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt index 16ec1a933d92..4f77b00b7570 100644 --- a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt +++ b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt @@ -26,7 +26,6 @@ import androidx.activity.enableEdgeToEdge import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.withInfiniteAnimationFrameNanos -import androidx.compose.foundation.Canvas import androidx.compose.foundation.border import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.forEachGesture @@ -45,6 +44,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.currentRecomposeScope import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -64,7 +64,6 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.toUpperCase import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -75,6 +74,9 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.window.layout.FoldingFeature import androidx.window.layout.WindowInfoTracker +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import java.lang.Float.max import java.lang.Float.min import java.util.Calendar @@ -83,9 +85,6 @@ import kotlin.math.absoluteValue import kotlin.math.floor import kotlin.math.sqrt import kotlin.random.Random -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch enum class RandomSeedType { Fixed, @@ -139,7 +138,6 @@ fun getDessertCode(): String = else -> Build.VERSION.RELEASE_OR_CODENAME.replace(Regex("[a-z]*"), "") } - val DEBUG_TEXT = mutableStateOf("Hello Universe") const val SHOW_DEBUG_TEXT = false @@ -158,7 +156,7 @@ fun DebugText(text: MutableState<String>) { } @Composable -fun Telemetry(universe: VisibleUniverse) { +fun Telemetry(universe: Universe) { var topVisible by remember { mutableStateOf(false) } var bottomVisible by remember { mutableStateOf(false) } @@ -180,10 +178,15 @@ fun Telemetry(universe: VisibleUniverse) { topVisible = true } - universe.triggerDraw.value // recompose on every frame - val explored = universe.planets.filter { it.explored } + // TODO: Narrow the scope of invalidation here to the specific data needed; + // the behavior below mimics the previous implementation of a snapshot ticker value + val recomposeScope = currentRecomposeScope + Telescope(universe) { + recomposeScope.invalidate() + } + BoxWithConstraints( modifier = Modifier.fillMaxSize().padding(6.dp).windowInsetsPadding(WindowInsets.safeContent), @@ -299,7 +302,7 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() - val universe = VisibleUniverse(namer = Namer(resources), randomSeed = randomSeed()) + val universe = Universe(namer = Namer(resources), randomSeed = randomSeed()) if (TEST_UNIVERSE) { universe.initTest() @@ -373,7 +376,7 @@ class MainActivity : ComponentActivity() { @Preview(name = "tablet", device = Devices.TABLET) @Composable fun MainActivityPreview() { - val universe = VisibleUniverse(namer = Namer(Resources.getSystem()), randomSeed = randomSeed()) + val universe = Universe(namer = Namer(Resources.getSystem()), randomSeed = randomSeed()) universe.initTest() @@ -458,12 +461,12 @@ fun FlightStick( @Composable fun Spaaaace( modifier: Modifier, - u: VisibleUniverse, + u: Universe, foldState: MutableState<FoldingFeature?> = mutableStateOf(null) ) { LaunchedEffect(u) { while (true) withInfiniteAnimationFrameNanos { frameTimeNanos -> - u.simulateAndDrawFrame(frameTimeNanos) + u.step(frameTimeNanos) } } @@ -492,7 +495,7 @@ fun Spaaaace( val centerFracY: Float by animateFloatAsState(if (halfFolded && horizontalFold) 0.25f else 0.5f, label = "centerY") - Canvas(modifier = canvasModifier) { + UniverseCanvas(u, canvasModifier) { u -> drawRect(Colors.Eigengrau, Offset.Zero, size) val closest = u.closestPlanet() diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt b/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt index d14234ec66d9..b8c68818888a 100644 --- a/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt +++ b/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt @@ -17,6 +17,8 @@ package com.android.egg.landroid import android.util.ArraySet +import androidx.compose.ui.util.fastForEach +import kotlinx.coroutines.DisposableHandle import kotlin.random.Random // artificially speed up or slow down the simulation @@ -127,6 +129,7 @@ open class Simulator(val randomSeed: Long) { val rng = Random(randomSeed) val entities = ArraySet<Entity>(1000) val constraints = ArraySet<Constraint>(100) + private val simStepListeners = mutableListOf<() -> Unit>() fun add(e: Entity) = entities.add(e) fun remove(e: Entity) = entities.remove(e) @@ -169,5 +172,26 @@ open class Simulator(val randomSeed: Long) { // 3. compute new velocities from updated positions and saved positions postUpdateAll(dt, localEntities) + + // 4. notify listeners that step is complete + simStepListeners.fastForEach { it.invoke() } + } + + /** + * Register [listener] to be invoked every time the [Simulator] completes one [step]. + * Use this to enqueue drawing. + * + * Instead of the usual register()/unregister() pattern, we're going to borrow + * [kotlinx.coroutines.DisposableHandle] here. Call [DisposableHandle.dispose] on the return + * value to unregister. + */ + fun addSimulationStepListener(listener: () -> Unit): DisposableHandle { + // add to listener list + simStepListeners += listener + + return DisposableHandle { + // on dispose, remove from listener list + simStepListeners -= listener + } } } diff --git a/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt b/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt index ed3ebc7bf9a5..c476d5cf0b8b 100644 --- a/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt +++ b/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt @@ -16,19 +16,31 @@ package com.android.egg.landroid +import androidx.compose.foundation.layout.Spacer +import androidx.compose.runtime.Composable +import androidx.compose.runtime.RememberObserver +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.PointMode +import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.rotateRad import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.invalidateDraw import androidx.compose.ui.util.lerp import androidx.core.math.MathUtils.clamp import com.android.egg.flags.Flags.flagFlag +import kotlinx.coroutines.DisposableHandle import java.lang.Float.max import kotlin.math.exp import kotlin.math.sqrt @@ -55,22 +67,108 @@ fun DrawScope.zoom(zoom: Float, block: ZoomedDrawScope.() -> Unit) { ds.scale(zoom) { block(ds) } } -class VisibleUniverse(namer: Namer, randomSeed: Long) : Universe(namer, randomSeed) { - // Magic variable. Every time we update it, Compose will notice and redraw the universe. - val triggerDraw = mutableStateOf(0L) +/** + * A device for observing changes to a [Simulator] such as a [Universe]. + * [observer] will be invoked each time a [Simulator.step] has completed. + */ +@Composable +fun <S : Simulator> Telescope( + subject: S, + observer: (S) -> Unit +) { + remember(subject) { + object : RememberObserver { + lateinit var registration: DisposableHandle + var currentObserver by mutableStateOf(observer) + + override fun onRemembered() { + registration = subject.addSimulationStepListener { currentObserver(subject) } + } + + override fun onForgotten() { + registration.dispose() + } + + override fun onAbandoned() {} + } + }.currentObserver = observer +} + +fun Modifier.drawUniverse( + universe: Universe, + draw: DrawScope.(Universe) -> Unit +): Modifier = this then UniverseElement(universe, draw) + +@Composable +fun UniverseCanvas( + universe: Universe, + modifier: Modifier = Modifier, + draw: DrawScope.(Universe) -> Unit +) { + Spacer(modifier.drawUniverse(universe, draw)) +} + +private class UniverseElement( + val universe: Universe, + val draw: DrawScope.(Universe) -> Unit +) : ModifierNodeElement<UniverseModifierNode>() { + override fun create(): UniverseModifierNode = UniverseModifierNode(universe, draw) + + // Called when a modifier is applied to a Layout whose inputs have changed + override fun update(node: UniverseModifierNode) { + node.universe = universe + node.draw = draw + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false - fun simulateAndDrawFrame(nanos: Long) { - // By writing this value, Compose will look for functions that read it (like drawZoomed). - triggerDraw.value = nanos + other as UniverseElement - step(nanos) + if (universe != other.universe) return false + if (draw != other.draw) return false + + return true + } + + override fun hashCode(): Int { + var result = universe.hashCode() + result = 31 * result + draw.hashCode() + return result } } -fun ZoomedDrawScope.drawUniverse(universe: VisibleUniverse) { - with(universe) { - triggerDraw.value // Please recompose when this value changes. +private class UniverseModifierNode( + universe: Universe, + draw: DrawScope.(Universe) -> Unit, +) : Modifier.Node(), DrawModifierNode { + private val universeListener: () -> Unit = { invalidateDraw() } + private var removeUniverseListener: DisposableHandle? = + universe.addSimulationStepListener(universeListener) + + var universe: Universe = universe + set(value) { + if (field === value) return + removeUniverseListener?.dispose() + field = value + removeUniverseListener = value.addSimulationStepListener(universeListener) + } + + var draw: ContentDrawScope.(Universe) -> Unit = draw + set(value) { + if (field === value) return + field = value + invalidateDraw() + } + override fun ContentDrawScope.draw() { + draw(universe) + } +} + +fun ZoomedDrawScope.drawUniverse(universe: Universe) { + with(universe) { constraints.forEach { when (it) { is Landing -> drawLanding(it) diff --git a/packages/SettingsLib/CardPreference/res/drawable/settingslib_card_preference_background.xml b/packages/SettingsLib/CardPreference/res/drawable/settingslib_card_preference_background.xml new file mode 100644 index 000000000000..1d57c1617495 --- /dev/null +++ b/packages/SettingsLib/CardPreference/res/drawable/settingslib_card_preference_background.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2025 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. +--> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="?android:colorControlHighlight"> + <item> + <shape android:shape="rectangle"> + <solid + android:color="@color/settingslib_materialColorSecondaryContainer" /> + <corners + android:radius="@dimen/settingslib_expressive_radius_extralarge3" /> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml b/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml index 9018baca79e7..4ce106e56822 100644 --- a/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml +++ b/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml @@ -14,9 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. --> -<com.google.android.material.card.MaterialCardView +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - style="@style/SettingsLibCardStyle"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4"> <LinearLayout android:id="@+id/card_container" @@ -24,10 +28,10 @@ android:layout_height="wrap_content" android:baselineAligned="false" android:minHeight="@dimen/settingslib_expressive_space_large3" - android:paddingStart="@dimen/settingslib_expressive_space_small1" - android:paddingEnd="@dimen/settingslib_expressive_space_small1" + android:paddingHorizontal="@dimen/settingslib_expressive_space_medium1" android:orientation="horizontal" - android:gravity="center_vertical"> + android:gravity="center_vertical" + android:background="@drawable/settingslib_card_preference_background"> <LinearLayout android:id="@+id/icon_frame" @@ -35,15 +39,13 @@ android:layout_height="wrap_content" android:minWidth="@dimen/settingslib_expressive_space_medium3" android:minHeight="@dimen/settingslib_expressive_space_medium3" - android:gravity="center" - android:orientation="horizontal"> - + android:gravity="center"> <ImageView android:id="@android:id/icon" android:layout_width="@dimen/settingslib_expressive_space_medium3" android:layout_height="@dimen/settingslib_expressive_space_medium3" - android:scaleType="centerInside"/> - + android:scaleType="centerInside" + android:importantForAccessibility="no"/> </LinearLayout> <LinearLayout @@ -54,19 +56,16 @@ android:paddingHorizontal="@dimen/settingslib_expressive_space_small1" android:paddingVertical="@dimen/settingslib_expressive_space_small2" android:orientation="vertical"> - <TextView android:id="@android:id/title" android:layout_width="match_parent" android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.CardTitle.SettingsLib"/> - + android:textAppearance="@style/TextAppearance.CardTitle.SettingsLib" /> <TextView android:id="@android:id/summary" android:layout_width="match_parent" android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.CardSummary.SettingsLib"/> - + android:textAppearance="@style/TextAppearance.CardSummary.SettingsLib" /> </LinearLayout> <ImageView @@ -75,9 +74,9 @@ android:layout_height="@dimen/settingslib_expressive_space_medium4" android:padding="@dimen/settingslib_expressive_space_extrasmall4" android:layout_gravity="center" + android:contentDescription="@string/settingslib_dismiss_button_content_description" android:src="@drawable/settingslib_expressive_icon_close" - android:background="?android:attr/selectableItemBackground" /> + android:tint="@color/settingslib_materialColorOnSecondary" /> </LinearLayout> - -</com.google.android.material.card.MaterialCardView>
\ No newline at end of file +</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml b/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml index 287b13fa0d50..e7d4a0013896 100644 --- a/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml +++ b/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml @@ -18,11 +18,11 @@ <resources> <style name="TextAppearance.CardTitle.SettingsLib" parent="@style/TextAppearance.SettingsLib.TitleMedium.Emphasized"> - <item name="android:textColor">@color/settingslib_materialColorOnPrimary</item> + <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item> </style> <style name="TextAppearance.CardSummary.SettingsLib" parent="@style/TextAppearance.SettingsLib.LabelMedium"> - <item name="android:textColor">@color/settingslib_materialColorOnSecondary</item> + <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item> </style> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values/strings.xml b/packages/SettingsLib/SettingsTheme/res/values/strings.xml index c36dcb88b9fe..f3f077edc91d 100644 --- a/packages/SettingsLib/SettingsTheme/res/values/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/strings.xml @@ -21,4 +21,6 @@ <string name="settingslib_expressive_text_expand">Expand</string> <!-- text of button to indicate user the textView is collapsable [CHAR LIMIT=NONE] --> <string name="settingslib_expressive_text_collapse">Collapse</string> + <!-- Content description of the dismiss button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="settingslib_dismiss_button_content_description">Dismiss</string> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index bbe08f254283..d94450b1cabd 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -219,3 +219,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "adopt_primary_group_management_api" + namespace: "cross_device_experiences" + description: "Adopt Bluetooth LE broadcast primary group management APIs" + bug: "381946931" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java index 4b7cb36f2753..bf86911ee683 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java @@ -134,6 +134,8 @@ public class CsipDeviceManager { // Do nothing if GroupId has been assigned if (!isValidGroupId(cachedDevice.getGroupId())) { final int newGroupId = getBaseGroupId(cachedDevice.getDevice()); + log("updateCsipDevices: propose new group id " + newGroupId + " for device " + + cachedDevice.getDevice()); // Do nothing if there is no GroupId on Bluetooth device if (isValidGroupId(newGroupId)) { cachedDevice.setGroupId(newGroupId); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java index 7c24df9e9019..ff5e9e657213 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -358,6 +358,9 @@ public class LocalBluetoothProfileManager { && mProfile instanceof CsipSetCoordinatorProfile; if (isAshaProfile && (newState == BluetoothProfile.STATE_CONNECTED)) { + if (DEBUG) { + Log.d(TAG, "onReceive, hearing aid profile connected, check hisyncid"); + } // Check if the HiSyncID has being initialized if (cachedDevice.getHiSyncId() == BluetoothHearingAid.HI_SYNC_ID_INVALID) { long newHiSyncId = getHearingAidProfile().getHiSyncId(cachedDevice.getDevice()); @@ -375,7 +378,9 @@ public class LocalBluetoothProfileManager { } if (isHapClientOrLeAudioProfile && newState == BluetoothProfile.STATE_CONNECTED) { - + if (DEBUG) { + Log.d(TAG, "onReceive, hap/lea profile connected, check hearing aid info"); + } // Checks if both profiles are connected to the device. Hearing aid info need // to be retrieved from these profiles separately. if (cachedDevice.isConnectedLeAudioHearingAidDevice()) { @@ -389,10 +394,16 @@ public class LocalBluetoothProfileManager { } if (isCsipProfile && (newState == BluetoothProfile.STATE_CONNECTED)) { + if (DEBUG) { + Log.d(TAG, "onReceive, csip profile connected, check group id"); + } // Check if the GroupID has being initialized if (cachedDevice.getGroupId() == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { final Map<Integer, ParcelUuid> groupIdMap = getCsipSetCoordinatorProfile() .getGroupUuidMapByDevice(cachedDevice.getDevice()); + if (DEBUG) { + Log.d(TAG, "csip group uuid map = " + groupIdMap); + } if (groupIdMap != null) { for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) { if (entry.getValue().equals(BluetoothUuid.CAP)) { @@ -431,6 +442,9 @@ public class LocalBluetoothProfileManager { mProfile.getProfileId()); } if (needDispatchProfileConnectionState) { + if (DEBUG) { + Log.d(TAG, "needDispatchProfileConnectionState"); + } cachedDevice.refresh(); mEventManager.dispatchProfileConnectionStateChanged(cachedDevice, newState, mProfile.getProfileId()); diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java index 84afb9f7a7e2..a1cf409733fb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java @@ -37,10 +37,10 @@ public abstract class AbstractPreferenceController { private static final String TAG = "AbstractPrefController"; - protected final Context mContext; + protected final @NonNull Context mContext; private final DevicePolicyManager mDevicePolicyManager; - public AbstractPreferenceController(Context context) { + public AbstractPreferenceController(@NonNull Context context) { mContext = context; mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt index 9aaefe47fda2..58c7907f77de 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt @@ -37,8 +37,9 @@ class FakeZenModeRepository : ZenModeRepository { override val globalZenMode: StateFlow<Int> get() = mutableZenMode.asStateFlow() - private val mutableModesFlow: MutableStateFlow<List<ZenMode>> = + private val mutableModesFlow: MutableStateFlow<List<ZenMode>> by lazy { MutableStateFlow(listOf(TestModeBuilder.MANUAL_DND)) + } override val modes: Flow<List<ZenMode>> get() = mutableModesFlow.asStateFlow() diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 6681c014f2e0..b0309a8fa5a5 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -454,6 +454,5 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, new InclusiveIntegerRangeValidator(0, 1)); VALIDATORS.put(Secure.ADVANCED_PROTECTION_MODE, BOOLEAN_VALIDATOR); - VALIDATORS.put(Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index bf3afeda448e..0f6311552de9 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -408,77 +408,8 @@ public class SettingsState { Slog.w(LOG_TAG, "Bulk sync request to acongid failed."); } } - - if (Flags.disableBulkCompare()) { - return; - } - - // TOBO(b/312444587): remove the comparison logic after Test Mission 2. - if (requests == null) { - Map<String, AconfigdFlagInfo> aconfigdFlagMap = - AconfigdJavaUtils.listFlagsValueInNewStorage(localSocket); - compareFlagValueInNewStorage( - mAconfigDefaultFlags, - aconfigdFlagMap); - } - } - } - } - - // TODO(b/312444587): remove the comparison logic after Test Mission 2. - public int compareFlagValueInNewStorage( - Map<String, AconfigdFlagInfo> defaultFlagMap, - Map<String, AconfigdFlagInfo> aconfigdFlagMap) { - - // Get all defaults from the default map. The mSettings may not contain - // all flags, since it only contains updated flags. - int diffNum = 0; - for (Map.Entry<String, AconfigdFlagInfo> entry : defaultFlagMap.entrySet()) { - String key = entry.getKey(); - AconfigdFlagInfo flag = entry.getValue(); - - AconfigdFlagInfo aconfigdFlag = aconfigdFlagMap.get(key); - if (aconfigdFlag == null) { - Slog.w(LOG_TAG, String.format("Flag %s is missing from aconfigd", key)); - diffNum++; - continue; - } - String diff = flag.dumpDiff(aconfigdFlag); - if (!diff.isEmpty()) { - Slog.w( - LOG_TAG, - String.format( - "Flag %s is different in Settings and aconfig: %s", key, diff)); - diffNum++; - } - } - - for (String key : aconfigdFlagMap.keySet()) { - if (defaultFlagMap.containsKey(key)) continue; - Slog.w(LOG_TAG, String.format("Flag %s is missing from Settings", key)); - diffNum++; - } - - String compareMarkerName = "aconfigd_marker/compare_diff_num"; - synchronized (mLock) { - Setting markerSetting = mSettings.get(compareMarkerName); - if (markerSetting == null) { - markerSetting = - new Setting( - compareMarkerName, - String.valueOf(diffNum), - false, - "aconfig", - "aconfig"); - mSettings.put(compareMarkerName, markerSetting); } - markerSetting.value = String.valueOf(diffNum); - } - - if (diffNum == 0) { - Slog.w(LOG_TAG, "Settings and new storage have same flags."); } - return diffNum; } @GuardedBy("mLock") diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig index cfd27c69032e..4fc3b873aa75 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig +++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig @@ -100,14 +100,4 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -} - -flag { - name: "disable_bulk_compare" - namespace: "core_experiments_team_internal" - description: "Disable bulk comparison between DeviceConfig and aconfig storage." - bug: "312444587" - metadata { - purpose: PURPOSE_BUGFIX - } }
\ No newline at end of file diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 9aad5d5f8367..cbdb36fff98c 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -689,7 +689,6 @@ public class SettingsBackupTest { Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, Settings.Secure.DEVICE_PAIRED, Settings.Secure.DIALER_DEFAULT_APPLICATION, - Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, Settings.Secure.DISABLED_PRINT_SERVICES, Settings.Secure.DISABLE_SECURE_WINDOWS, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java index 276b206cd6a1..6e7576631147 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -1303,85 +1303,4 @@ public class SettingsStateTest { assertFalse(flag3.getHasServerOverride()); assertFalse(flag3.getHasLocalOverride()); } - - @Test - @RequiresFlagsDisabled(Flags.FLAG_DISABLE_BULK_COMPARE) - public void testCompareFlagValueInNewStorage() { - int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0); - Object lock = new Object(); - SettingsState settingsState = - new SettingsState( - InstrumentationRegistry.getContext(), - lock, - mSettingsFile, - configKey, - SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, - Looper.getMainLooper()); - - AconfigdFlagInfo defaultFlag1 = - AconfigdFlagInfo.newBuilder() - .setPackageName("com.android.flags") - .setFlagName("flag1") - .setDefaultFlagValue("false") - .setServerFlagValue("true") - .setHasServerOverride(true) - .setIsReadWrite(true) - .build(); - - AconfigdFlagInfo expectedFlag1 = - AconfigdFlagInfo.newBuilder() - .setPackageName("com.android.flags") - .setFlagName("flag1") - .setServerFlagValue("true") - .setDefaultFlagValue("false") - .setHasServerOverride(true) - .setIsReadWrite(true) - .build(); - - Map<String, AconfigdFlagInfo> aconfigdMap = new HashMap<>(); - Map<String, AconfigdFlagInfo> defaultMap = new HashMap<>(); - - defaultMap.put("com.android.flags.flag1", defaultFlag1); - aconfigdMap.put("com.android.flags.flag1", expectedFlag1); - - int ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap); - assertEquals(0, ret); - - String value = - settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue(); - assertEquals("0", value); - - AconfigdFlagInfo defaultFlag2 = - AconfigdFlagInfo.newBuilder() - .setPackageName("com.android.flags") - .setFlagName("flag2") - .setDefaultFlagValue("false") - .build(); - defaultMap.put("com.android.flags.flag2", defaultFlag2); - - ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap); - // missing from new storage - assertEquals(1, ret); - value = - settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue(); - assertEquals("1", value); - - AconfigdFlagInfo expectedFlag2 = - AconfigdFlagInfo.newBuilder() - .setPackageName("com.android.flags") - .setFlagName("flag2") - .setServerFlagValue("true") - .setLocalFlagValue("true") - .setDefaultFlagValue("false") - .setHasServerOverride(true) - .setHasLocalOverride(true) - .build(); - aconfigdMap.put("com.android.flags.flag2", expectedFlag2); - ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap); - // skip the server and local value comparison when the flag is read_only - assertEquals(0, ret); - value = - settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue(); - assertEquals("0", value); - } } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 0075f85af8ed..b53198d8ae98 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -406,7 +406,7 @@ android:killAfterRestore="false" android:hardwareAccelerated="true" android:label="@string/app_label" - android:icon="@drawable/android15_patch_adaptive" + android:icon="@drawable/android16_patch_adaptive" android:process="com.android.systemui" android:supportsRtl="true" android:theme="@style/Theme.SystemUI" diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml index 0f210e7e5e7b..b40a11469172 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml @@ -20,7 +20,10 @@ <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/> <uses-permission android:name="android.permission.MANAGE_USERS"/> - <application android:supportsRtl="true"> + <application + android:supportsRtl="true" + android:allowBackup="true" + android:restoreAnyVersion="true"> <service android:name="com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService" android:exported="false" diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp index 1858c80ca901..088ec136f24e 100644 --- a/packages/SystemUI/aconfig/Android.bp +++ b/packages/SystemUI/aconfig/Android.bp @@ -23,6 +23,7 @@ package { default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_", default_visibility: [ "//visibility:override", + "//frameworks/base/libs/WindowManager/Shell:__subpackages__", "//frameworks/base/packages/SystemUI:__subpackages__", "//frameworks/libs/systemui/tracinglib:__subpackages__", "//frameworks/base/services/accessibility:__subpackages__", diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 153f89284587..b33421d5d4ca 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1514,16 +1514,6 @@ flag { } flag { - name: "sim_pin_talkback_fix_for_double_submit" - namespace: "systemui" - description: "The SIM PIN entry screens show the wrong message due" - bug: "346932439" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "sim_pin_bouncer_reset" namespace: "systemui" description: "The SIM PIN bouncer does not close after unlocking" diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java index 2e8f92839fa6..4b8610884b05 100644 --- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java +++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java @@ -185,16 +185,24 @@ public class ViewUIComponent implements UIComponent { return; } ViewGroup.LayoutParams params = mView.getLayoutParams(); - if (params == null || params.width == 0 || params.height == 0) { + if (params == null) { // layout pass didn't happen. logD("draw: skipped - no layout"); return; } + + final Rect realBounds = getRealBounds(); + if (realBounds.width() == 0 || realBounds.height() == 0) { + // bad bounds. + logD("draw: skipped - zero bounds"); + return; + } + + Canvas canvas = mSurface.lockHardwareCanvas(); // Clear the canvas first. canvas.drawColor(0, PorterDuff.Mode.CLEAR); if (mVisibleOverride) { - Rect realBounds = getRealBounds(); Rect renderBounds = getBounds(); canvas.translate(renderBounds.left, renderBounds.top); canvas.scale( diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt index 311519122312..d08d859ec0d7 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt @@ -19,9 +19,11 @@ package com.android.compose.ui.graphics import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.graphics.drawscope.DrawScope @@ -30,14 +32,14 @@ import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.graphics.layer.drawLayer import androidx.compose.ui.layout.LayoutCoordinates -import androidx.compose.ui.layout.layout +import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.modifier.ModifierLocalModifierNode import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.LayoutAwareModifierNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.node.requireDensity import androidx.compose.ui.node.requireGraphicsContext -import androidx.compose.ui.node.requireLayoutCoordinates import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.util.fastForEach @@ -48,17 +50,7 @@ import androidx.compose.ui.util.fastForEach * The elements redirected to this container will be drawn above the content of this composable. */ fun Modifier.container(state: ContainerState): Modifier { - return layout { measurable, constraints -> - val p = measurable.measure(constraints) - layout(p.width, p.height) { - val coords = coordinates - if (coords != null && !isLookingAhead) { - state.lastCoords = coords - } - - p.place(0, 0) - } - } + return onPlaced { state.lastOffsetInWindow = it.positionInWindow() } .drawWithContent { drawContent() state.drawInOverlay(this) @@ -91,7 +83,7 @@ fun Modifier.drawInContainer( class ContainerState { private var renderers = mutableStateListOf<LayerRenderer>() - internal var lastCoords: LayoutCoordinates? = null + internal var lastOffsetInWindow by mutableStateOf(Offset.Zero) internal fun onLayerRendererAttached(renderer: LayerRenderer) { renderers.add(renderer) @@ -142,7 +134,8 @@ internal class DrawInContainerNode( var enabled: () -> Boolean = { true }, zIndex: Float = 0f, var clipPath: (LayoutDirection, Density) -> Path? = { _, _ -> null }, -) : Modifier.Node(), DrawModifierNode, ModifierLocalModifierNode { +) : Modifier.Node(), LayoutAwareModifierNode, DrawModifierNode, ModifierLocalModifierNode { + private var lastOffsetInWindow by mutableStateOf(Offset.Zero) var zIndex by mutableFloatStateOf(zIndex) private inner class LayerWithRenderer(val layer: GraphicsLayer) : LayerRenderer { @@ -152,11 +145,7 @@ internal class DrawInContainerNode( override fun drawInOverlay(drawScope: DrawScope) { if (enabled()) { with(drawScope) { - val containerCoords = - checkNotNull(state.lastCoords) { "container is not placed" } - val (x, y) = - requireLayoutCoordinates().positionInWindow() - - containerCoords.positionInWindow() + val (x, y) = lastOffsetInWindow - state.lastOffsetInWindow val clipPath = clipPath(layoutDirection, requireDensity()) if (clipPath != null) { clipPath(clipPath) { translate(x, y) { drawLayer(layer) } } @@ -178,6 +167,10 @@ internal class DrawInContainerNode( } } + override fun onPlaced(coordinates: LayoutCoordinates) { + lastOffsetInWindow = coordinates.positionInWindow() + } + val layer: GraphicsLayer? get() = layerWithRenderer?.layer diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt index 0054a4c899ec..439968590dad 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt @@ -168,7 +168,7 @@ private fun StandardLayout(viewModel: BouncerSceneContentViewModel, modifier: Mo LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded FoldAware( - modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 48.dp), + modifier = modifier.padding(top = 92.dp, bottom = 48.dp), viewModel = viewModel, aboveFold = { Column( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index 8321238b28b1..3d0354a578f7 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -24,6 +24,8 @@ import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme @@ -35,6 +37,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Offset @@ -45,6 +48,7 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.integerResource +import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Easings @@ -212,23 +216,27 @@ fun PatternBouncer( var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } var offset: Offset by remember { mutableStateOf(Offset.Zero) } var scale: Float by remember { mutableStateOf(1f) } + // This is the size of the drawing area, in dips. + val dotDrawingArea = + remember(colCount, rowCount) { + DpSize( + // Because the width also includes spacing to the left and right of the leftmost and + // rightmost dots in the grid and because UX mocks specify the width without that + // spacing, the actual width needs to be defined slightly bigger than the UX mock + // width. + width = (262 * colCount / 2).dp, + // Because the height also includes spacing above and below the topmost and + // bottommost + // dots in the grid and because UX mocks specify the height without that spacing, + // the + // actual height needs to be defined slightly bigger than the UX mock height. + height = (262 * rowCount / 2).dp, + ) + } - Canvas( - modifier - .sysuiResTag("bouncer_pattern_root") - // Because the width also includes spacing to the left and right of the leftmost and - // rightmost dots in the grid and because UX mocks specify the width without that - // spacing, the actual width needs to be defined slightly bigger than the UX mock width. - .width((262 * colCount / 2).dp) - // Because the height also includes spacing above and below the topmost and bottommost - // dots in the grid and because UX mocks specify the height without that spacing, the - // actual height needs to be defined slightly bigger than the UX mock height. - .height((262 * rowCount / 2).dp) - // Need to clip to bounds to make sure that the lines don't follow the input pointer - // when it leaves the bounds of the dot grid. - .clipToBounds() - .onGloballyPositioned { coordinates -> gridCoordinates = coordinates } - .thenIf(isInputEnabled) { + Box( + modifier = + modifier.fillMaxWidth().thenIf(isInputEnabled) { Modifier.pointerInput(Unit) { awaitEachGesture { awaitFirstDown() @@ -257,105 +265,125 @@ fun PatternBouncer( inputPosition = change.position change.position.minus(offset).div(scale).let { viewModel.onDrag( - xPx = it.x, + xPx = + it.x - + ((size.width - dotDrawingArea.width.roundToPx()) / 2), yPx = it.y, - containerSizePx = size.width, + containerSizePx = dotDrawingArea.width.roundToPx(), ) } } } } - .motionTestValues { - entryAnimationCompleted exportAs entryCompleted - dotAppearFadeInAnimatables.map { it.value.value } exportAs dotAppearFadeIn - dotAppearMoveUpAnimatables.map { it.value.value } exportAs dotAppearMoveUp - dotScalingAnimatables.map { it.value.value } exportAs dotScaling - } ) { - gridCoordinates?.let { nonNullCoordinates -> - val containerSize = nonNullCoordinates.size - if (containerSize.width <= 0 || containerSize.height <= 0) { - return@let - } + Canvas( + Modifier.sysuiResTag("bouncer_pattern_root") + .width(dotDrawingArea.width) + .height(dotDrawingArea.height) + // Need to clip to bounds to make sure that the lines don't follow the input pointer + // when it leaves the bounds of the dot grid. + .clipToBounds() + .align(Alignment.Center) + .onGloballyPositioned { coordinates -> gridCoordinates = coordinates } + .motionTestValues { + entryAnimationCompleted exportAs entryCompleted + dotAppearFadeInAnimatables.map { it.value.value } exportAs dotAppearFadeIn + dotAppearMoveUpAnimatables.map { it.value.value } exportAs dotAppearMoveUp + dotScalingAnimatables.map { it.value.value } exportAs dotScaling + } + ) { + gridCoordinates?.let { nonNullCoordinates -> + val containerSize = nonNullCoordinates.size + if (containerSize.width <= 0 || containerSize.height <= 0) { + return@let + } - val horizontalSpacing = containerSize.width.toFloat() / colCount - val verticalSpacing = containerSize.height.toFloat() / rowCount - val spacing = min(horizontalSpacing, verticalSpacing) - val horizontalOffset = - offset( - availableSize = containerSize.width, - spacingPerDot = spacing, - dotCount = colCount, - isCentered = true, - ) - val verticalOffset = - offset( - availableSize = containerSize.height, - spacingPerDot = spacing, - dotCount = rowCount, - isCentered = centerDotsVertically, - ) - offset = Offset(horizontalOffset, verticalOffset) - scale = (colCount * spacing) / containerSize.width + val horizontalSpacing = containerSize.width.toFloat() / colCount + val verticalSpacing = containerSize.height.toFloat() / rowCount + val spacing = min(horizontalSpacing, verticalSpacing) + val horizontalOffset = + offset( + availableSize = containerSize.width, + spacingPerDot = spacing, + dotCount = colCount, + isCentered = true, + ) + val verticalOffset = + offset( + availableSize = containerSize.height, + spacingPerDot = spacing, + dotCount = rowCount, + isCentered = centerDotsVertically, + ) + offset = Offset(horizontalOffset, verticalOffset) + scale = (colCount * spacing) / containerSize.width - if (isAnimationEnabled) { - // Draw lines between dots. - selectedDots.forEachIndexed { index, dot -> - if (index > 0) { - val previousDot = selectedDots[index - 1] - val lineFadeOutAnimationProgress = - lineFadeOutAnimatables[previousDot]!!.value - val startLerp = 1 - lineFadeOutAnimationProgress - val from = - pixelOffset(previousDot, spacing, horizontalOffset, verticalOffset) - val to = pixelOffset(dot, spacing, horizontalOffset, verticalOffset) - val lerpedFrom = - Offset( - x = from.x + (to.x - from.x) * startLerp, - y = from.y + (to.y - from.y) * startLerp, + if (isAnimationEnabled) { + // Draw lines between dots. + selectedDots.forEachIndexed { index, dot -> + if (index > 0) { + val previousDot = selectedDots[index - 1] + val lineFadeOutAnimationProgress = + lineFadeOutAnimatables[previousDot]!!.value + val startLerp = 1 - lineFadeOutAnimationProgress + val from = + pixelOffset(previousDot, spacing, horizontalOffset, verticalOffset) + val to = pixelOffset(dot, spacing, horizontalOffset, verticalOffset) + val lerpedFrom = + Offset( + x = from.x + (to.x - from.x) * startLerp, + y = from.y + (to.y - from.y) * startLerp, + ) + drawLine( + start = lerpedFrom, + end = to, + cap = StrokeCap.Round, + alpha = lineFadeOutAnimationProgress * lineAlpha(spacing), + color = lineColor, + strokeWidth = lineStrokeWidth, ) - drawLine( - start = lerpedFrom, - end = to, - cap = StrokeCap.Round, - alpha = lineFadeOutAnimationProgress * lineAlpha(spacing), - color = lineColor, - strokeWidth = lineStrokeWidth, - ) + } } - } - // Draw the line between the most recently-selected dot and the input pointer - // position. - inputPosition?.let { lineEnd -> - currentDot?.let { dot -> - val from = pixelOffset(dot, spacing, horizontalOffset, verticalOffset) - val lineLength = - sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2)) - drawLine( - start = from, - end = lineEnd, - cap = StrokeCap.Round, - alpha = lineAlpha(spacing, lineLength), - color = lineColor, - strokeWidth = lineStrokeWidth, - ) + // Draw the line between the most recently-selected dot and the input pointer + // position. + inputPosition?.let { lineEnd -> + currentDot?.let { dot -> + val from = pixelOffset(dot, spacing, horizontalOffset, verticalOffset) + val lineLength = + sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2)) + drawLine( + start = from, + end = lineEnd, + cap = StrokeCap.Round, + alpha = lineAlpha(spacing, lineLength), + color = lineColor, + strokeWidth = lineStrokeWidth, + ) + } } } - } - // Draw each dot on the grid. - dots.forEach { dot -> - val initialOffset = checkNotNull(dotAppearMaxOffsetPixels[dot]) - val appearOffset = - (1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset - drawCircle( - center = - pixelOffset(dot, spacing, horizontalOffset, verticalOffset + appearOffset), - color = - dotColor.copy(alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value), - radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value, - ) + // Draw each dot on the grid. + dots.forEach { dot -> + val initialOffset = checkNotNull(dotAppearMaxOffsetPixels[dot]) + val appearOffset = + (1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset + drawCircle( + center = + pixelOffset( + dot, + spacing, + horizontalOffset, + verticalOffset + appearOffset, + ), + color = + dotColor.copy( + alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value + ), + radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value, + ) + } } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt index f2edec657cd4..3ae50369e9e3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt @@ -27,9 +27,14 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.pointer.motionEventSpy -import androidx.compose.ui.semantics.hideFromAccessibility +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.semantics.CustomAccessibilityAction +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.semantics +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.res.R @OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class) @Composable @@ -38,15 +43,38 @@ fun CommunalTouchableSurface( modifier: Modifier = Modifier, content: @Composable BoxScope.() -> Unit, ) { - + val context = LocalContext.current val interactionSource = remember { MutableInteractionSource() } Box( modifier = modifier - // The touchable surface is hidden for accessibility because these actions are - // already provided through custom accessibility actions. - .semantics { hideFromAccessibility() } + .semantics { + contentDescription = + context.getString( + R.string.accessibility_content_description_for_communal_hub + ) + customActions = + listOf( + CustomAccessibilityAction( + context.getString( + R.string.accessibility_action_label_close_communal_hub + ) + ) { + viewModel.changeScene( + CommunalScenes.Blank, + "closed by accessibility", + ) + true + }, + CustomAccessibilityAction( + context.getString(R.string.accessibility_action_label_edit_widgets) + ) { + viewModel.onOpenWidgetEditor() + true + }, + ) + } .combinedClickable( onLongClick = viewModel::onLongClick, onClick = viewModel::onClick, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index f7ce2153b0ec..7f7273d710a1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -75,6 +75,7 @@ import com.android.compose.animation.Expandable import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.fadingBackground import com.android.compose.theme.colorAttr +import com.android.systemui.Flags.notificationShadeBlur import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon @@ -163,14 +164,16 @@ fun FooterActions( } } - val backgroundColor = colorAttr(R.attr.underSurface) + val backgroundColor = + if (!notificationShadeBlur()) colorAttr(R.attr.underSurface) else Color.Transparent + val backgroundAlphaValue = if (!notificationShadeBlur()) backgroundAlpha::value else ({ 0f }) val contentColor = MaterialTheme.colorScheme.onSurface val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius) val backgroundModifier = - remember(backgroundColor, backgroundAlpha, backgroundTopRadius) { + remember(backgroundColor, backgroundAlphaValue, backgroundTopRadius) { Modifier.fadingBackground( backgroundColor, - backgroundAlpha::value, + backgroundAlphaValue, RoundedCornerShape(topStart = backgroundTopRadius, topEnd = backgroundTopRadius), ) } @@ -305,7 +308,8 @@ private fun NumberButton( ) { Box(Modifier.size(40.dp)) { Box( - Modifier.fillMaxSize() + Modifier + .fillMaxSize() .clip(CircleShape) .indication(interactionSource, LocalIndication.current) ) { @@ -333,7 +337,9 @@ private fun NewChangesDot(modifier: Modifier = Modifier) { val contentDescription = stringResource(R.string.fgs_dot_content_description) val color = MaterialTheme.colorScheme.tertiary - Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) { + Canvas(modifier + .size(12.dp) + .semantics { this.contentDescription = contentDescription }) { drawCircle(color) } } @@ -362,7 +368,9 @@ private fun TextButton( Modifier.padding(horizontal = dimensionResource(R.dimen.qs_footer_padding)), verticalAlignment = Alignment.CenterVertically, ) { - Icon(icon, Modifier.padding(end = 12.dp).size(20.dp)) + Icon(icon, Modifier + .padding(end = 12.dp) + .size(20.dp)) Text( text, @@ -383,7 +391,9 @@ private fun TextButton( Icon( painterResource(com.android.internal.R.drawable.ic_chevron_end), contentDescription = null, - Modifier.padding(start = 8.dp).size(20.dp), + Modifier + .padding(start = 8.dp) + .size(20.dp), ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt index f052e60246d2..50bae8a094ad 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt @@ -31,10 +31,14 @@ import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.boundsInWindow +import androidx.compose.ui.layout.onPlaced +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ContentScope @@ -59,6 +63,8 @@ import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.ui.composable.Overlay import com.android.systemui.shade.ui.composable.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.OverlayShade +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import com.android.systemui.statusbar.phone.ui.StatusBarIconController @@ -96,13 +102,37 @@ constructor( override fun ContentScope.Content(modifier: Modifier) { val viewModel = rememberViewModel("QuickSettingsShadeOverlay") { contentViewModelFactory.create() } + val panelCornerRadius = + with(LocalDensity.current) { OverlayShade.Dimensions.PanelCornerRadius.toPx().toInt() } + + // set the bounds to null when the QuickSettings overlay disappears + DisposableEffect(Unit) { onDispose { viewModel.onPanelShapeChanged(null) } } OverlayShade( panelAlignment = Alignment.TopEnd, modifier = modifier, onScrimClicked = viewModel::onScrimClicked, ) { - Column { + Column( + modifier = + Modifier.onPlaced { coordinates -> + val boundsInWindow = coordinates.boundsInWindow() + val shadeScrimBounds = + ShadeScrimBounds( + left = boundsInWindow.left, + top = boundsInWindow.top, + right = boundsInWindow.right, + bottom = boundsInWindow.bottom, + ) + val shape = + ShadeScrimShape( + bounds = shadeScrimBounds, + topRadius = 0, + bottomRadius = panelCornerRadius, + ) + viewModel.onPanelShapeChanged(shape) + } + ) { if (viewModel.showHeader) { CollapsedShadeHeader( viewModelFactory = viewModel.shadeHeaderViewModelFactory, @@ -112,7 +142,6 @@ constructor( statusBarIconController = statusBarIconController, ) } - ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/SensorLocation.kt index 2f2f3a35dbaa..552679224bca 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/SensorLocation.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.biometrics.shared.model +package com.android.systemui.shared.customization.data /** * Provides current sensor location information in the current screen resolution [scale]. @@ -26,18 +26,40 @@ data class SensorLocation( private val naturalCenterX: Int, private val naturalCenterY: Int, private val naturalRadius: Int, - private val scale: Float = 1f + private val scale: Float = 1f, ) { val centerX: Float get() { return naturalCenterX * scale } + val centerY: Float get() { return naturalCenterY * scale } + val radius: Float get() { return naturalRadius * scale } + + fun encode(): String { + return floatArrayOf( + naturalCenterX.toFloat(), + naturalCenterY.toFloat(), + naturalRadius.toFloat(), + scale, + ) + .joinToString(DELIMITER) + } + + companion object { + + private const val DELIMITER: String = "," + + fun decode(encoded: String): SensorLocation { + val array = encoded.split(DELIMITER).map { it.toFloat() }.toFloatArray() + return SensorLocation(array[0].toInt(), array[1].toInt(), array[2].toInt(), array[3]) + } + } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt index 48af2d9f5542..caa6636bde03 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt @@ -80,13 +80,6 @@ interface CustomizationProviderClient { fun observeFlags(): Flow<List<Flag>> /** - * Returns [Flow] for observing the variables from the System UI. - * - * @see [queryRuntimeValues] - */ - fun observeRuntimeValues(): Flow<Bundle> - - /** * Returns all available affordances supported by the device, regardless of current slot * placement. */ @@ -291,6 +284,9 @@ class CustomizationProviderClientImpl( Contract.RuntimeValuesTable.KEY_IS_SHADE_LAYOUT_WIDE -> { putBoolean(name, cursor.getInt(valueColumnIndex) == 1) } + Contract.RuntimeValuesTable.KEY_UDFPS_LOCATION -> { + putString(name, cursor.getString(valueColumnIndex)) + } } } } @@ -307,10 +303,6 @@ class CustomizationProviderClientImpl( return observeUri(Contract.FlagsTable.URI).map { queryFlags() } } - override fun observeRuntimeValues(): Flow<Bundle> { - return observeUri(Contract.RuntimeValuesTable.URI).map { queryRuntimeValues() } - } - override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> { return withContext(backgroundDispatcher) { context.contentResolver diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt index cb167eddcea9..2934f070b05f 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt @@ -19,6 +19,7 @@ package com.android.systemui.shared.customization.data.content import android.content.ContentResolver import android.net.Uri +import com.android.systemui.shared.customization.data.SensorLocation /** Contract definitions for querying content about keyguard quick affordances. */ object CustomizationProviderContract { @@ -213,6 +214,11 @@ object CustomizationProviderContract { * be as wide as the entire screen. */ const val KEY_IS_SHADE_LAYOUT_WIDE = "is_shade_layout_wide" + /** + * This key corresponds to a String value, representing the string form of [SensorLocation], + * which contains the information of the UDFPS location. + */ + const val KEY_UDFPS_LOCATION = "udfps_location" object Columns { /** String. Unique ID for the value. */ diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt index 47c5bda93c0e..70d17820a12c 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt @@ -108,10 +108,6 @@ class FakeCustomizationProviderClient( return flags.asStateFlow() } - override fun observeRuntimeValues(): Flow<Bundle> { - return runtimeValues.asStateFlow() - } - override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> { return affordances.value } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java index 4d1660e71c0a..e26e19d27417 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -79,8 +79,6 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { @Mock private LatencyTracker mLatencyTracker; @Mock - private LiftToActivateListener mLiftToactivateListener; - @Mock private EmergencyButtonController mEmergencyButtonController; private FalsingCollector mFalsingCollector = new FalsingCollectorFake(); @Mock @@ -122,7 +120,7 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_REVAMPED_BOUNCER_MESSAGES); mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView, mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, - mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener, + mKeyguardMessageAreaControllerFactory, mLatencyTracker, mEmergencyButtonController, mFalsingCollector, featureFlags, mSelectedUserInteractor, keyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 4d2a6d9bd57a..142a2868ec14 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -90,8 +90,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Mock private lateinit var mLatencyTracker: LatencyTracker - @Mock private lateinit var liftToActivateListener: LiftToActivateListener - @Mock private val mEmergencyButtonController: EmergencyButtonController? = null private val falsingCollector: FalsingCollector = FalsingCollectorFake() private val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository()) @@ -147,7 +145,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { mKeyguardSecurityCallback, keyguardMessageAreaControllerFactory, mLatencyTracker, - liftToActivateListener, mEmergencyButtonController, falsingCollector, postureController, diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt index 9cd52153eff6..c751a7db51dc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt @@ -63,7 +63,6 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { @Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory @Mock private lateinit var latencyTracker: LatencyTracker - @Mock private lateinit var liftToActivateListener: LiftToActivateListener @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var falsingCollector: FalsingCollector @Mock private lateinit var emergencyButtonController: EmergencyButtonController @@ -100,7 +99,6 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, - liftToActivateListener, telephonyManager, falsingCollector, emergencyButtonController, diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt index 3c229975eef5..c34682551eda 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt @@ -57,7 +57,6 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() { @Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory @Mock private lateinit var latencyTracker: LatencyTracker - @Mock private lateinit var liftToActivateListener: LiftToActivateListener @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var falsingCollector: FalsingCollector @Mock private lateinit var emergencyButtonController: EmergencyButtonController @@ -95,7 +94,6 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() { keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, - liftToActivateListener, telephonyManager, falsingCollector, emergencyButtonController, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt new file mode 100644 index 000000000000..fbb0fee2419c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2025 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.bouncer.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.kosmos.testScope +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.SystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class KeyguardBouncerRepositoryTest : SysuiTestCase() { + + @Mock private lateinit var systemClock: SystemClock + @Mock private lateinit var bouncerLogger: TableLogBuffer + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + lateinit var underTest: KeyguardBouncerRepository + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = + object : + KeyguardBouncerRepositoryImpl( + systemClock, + testScope.backgroundScope, + bouncerLogger, + ) { + override fun isDebuggable(): Boolean = true + } + } + + @Test + fun changingFlowValueTriggersLogging() = + testScope.runTest { + underTest.setPrimaryShow(true) + runCurrent() + Mockito.verify(bouncerLogger) + .logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any()) + } + + @Test + fun primaryStartDisappearAnimation() = + testScope.runTest { + assertThat(underTest.isPrimaryBouncerStartingDisappearAnimation()).isFalse() + + underTest.setPrimaryStartDisappearAnimation(Runnable {}) + assertThat(underTest.isPrimaryBouncerStartingDisappearAnimation()).isTrue() + + underTest.setPrimaryStartDisappearAnimation(null) + assertThat(underTest.isPrimaryBouncerStartingDisappearAnimation()).isFalse() + + val disappearFlow by collectValues(underTest.primaryBouncerStartingDisappearAnimation) + underTest.setPrimaryStartDisappearAnimation(null) + assertThat(disappearFlow[0]).isNull() + + // Now issue two in a row to make sure one is not dropped + underTest.setPrimaryStartDisappearAnimation(Runnable {}) + underTest.setPrimaryStartDisappearAnimation(null) + assertThat(disappearFlow[1]).isNotNull() + assertThat(disappearFlow[2]).isNull() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt index d5e1fae215c7..c1feca29906a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -105,7 +105,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { mSelectedUserInteractor, faceAuthInteractor, ) - whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) + whenever(repository.isPrimaryBouncerStartingDisappearAnimation()).thenReturn(false) whenever(repository.primaryBouncerShow.value).thenReturn(false) whenever(bouncerView.delegate).thenReturn(bouncerViewDelegate) resources = context.orCreateTestableResources @@ -199,7 +199,6 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @Test fun testExpansion_fullyShown() { whenever(repository.panelExpansionAmount.value).thenReturn(0.5f) - whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) underTest.setPanelExpansion(EXPANSION_VISIBLE) verify(falsingCollector).onBouncerShown() verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown() @@ -208,7 +207,6 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @Test fun testExpansion_fullyHidden() { whenever(repository.panelExpansionAmount.value).thenReturn(0.5f) - whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) underTest.setPanelExpansion(EXPANSION_HIDDEN) verify(repository).setPrimaryShow(false) verify(falsingCollector).onBouncerHidden() @@ -307,7 +305,6 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { fun testIsFullShowing() { whenever(repository.primaryBouncerShow.value).thenReturn(true) whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE) - whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) assertThat(underTest.isFullyShowing()).isTrue() whenever(repository.primaryBouncerShow.value).thenReturn(false) assertThat(underTest.isFullyShowing()).isFalse() @@ -333,9 +330,9 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @Test fun testIsAnimatingAway() { - whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {}) + whenever(repository.isPrimaryBouncerStartingDisappearAnimation()).thenReturn(true) assertThat(underTest.isAnimatingAway()).isTrue() - whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) + whenever(repository.isPrimaryBouncerStartingDisappearAnimation()).thenReturn(false) assertThat(underTest.isAnimatingAway()).isFalse() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt index ca7e2032be93..26859b6e10f8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt @@ -190,11 +190,8 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() { lockDevice() } - if (shadeMode == ShadeMode.Dual) { - assertThat(DualShade.isEnabled).isTrue() - } else { - assertThat(DualShade.isEnabled).isFalse() - kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode == ShadeMode.Split) + if (shadeMode !is ShadeMode.Dual) { + kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode is ShadeMode.Split) } runCurrent() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt index d6daa794c45b..e19ea365fa1f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt @@ -316,11 +316,8 @@ class DreamUserActionsViewModelTest : SysuiTestCase() { lockDevice() } - if (shadeMode == ShadeMode.Dual) { - assertThat(DualShade.isEnabled).isTrue() - } else { - assertThat(DualShade.isEnabled).isFalse() - kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode == ShadeMode.Split) + if (shadeMode !is ShadeMode.Dual) { + kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode is ShadeMode.Split) } runCurrent() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt index 79556baed067..fecd8c3cacca 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt @@ -26,11 +26,9 @@ import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.systemui.Flags import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake -import com.android.systemui.flags.setFlagValue import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -42,7 +40,6 @@ import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tiles.dialog.InternetDialogManager import com.android.systemui.qs.tiles.dialog.WifiStateWorker import com.android.systemui.res.R -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.statusbar.connectivity.AccessPointController import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun @@ -65,6 +62,7 @@ import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -150,11 +148,18 @@ class InternetTileNewImplTest(flags: FlagsParameterization) : SysuiTestCase() { ) underTest.initialize() + underTest.setListening(Object(), true) looper.processAllMessages() } + @After + fun tearDown() { + underTest.destroy() + looper.processAllMessages() + } + @Test fun noDefaultConnection_noNetworkAvailable() = testScope.runTest { @@ -272,33 +277,37 @@ class InternetTileNewImplTest(flags: FlagsParameterization) : SysuiTestCase() { underTest.click(null) looper.processAllMessages() - verify(dialogManager, times(1)).create( - aboveStatusBar = true, - accessPointController.canConfigMobileData(), - accessPointController.canConfigWifi(), - null, - ) + verify(dialogManager, times(1)) + .create( + aboveStatusBar = true, + accessPointController.canConfigMobileData(), + accessPointController.canConfigWifi(), + null, + ) } @Test @EnableFlags( - value = [ - QsDetailedView.FLAG_NAME, - FLAG_SCENE_CONTAINER, - KeyguardWmStateRefactor.FLAG_NAME, - NotificationThrottleHun.FLAG_NAME, - DualShade.FLAG_NAME] + value = + [ + QsDetailedView.FLAG_NAME, + FLAG_SCENE_CONTAINER, + KeyguardWmStateRefactor.FLAG_NAME, + NotificationThrottleHun.FLAG_NAME, + DualShade.FLAG_NAME, + ] ) fun click_withQsDetailedViewEnabled() { underTest.click(null) looper.processAllMessages() - verify(dialogManager, times(0)).create( - aboveStatusBar = true, - accessPointController.canConfigMobileData(), - accessPointController.canConfigWifi(), - null, - ) + verify(dialogManager, times(0)) + .create( + aboveStatusBar = true, + accessPointController.canConfigMobileData(), + accessPointController.canConfigWifi(), + null, + ) } companion object { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt index c7da03dbbf30..497e33536b7f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt @@ -46,6 +46,7 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import java.util.concurrent.Executors +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -123,6 +124,12 @@ class RecordIssueTileTest : SysuiTestCase() { ) } + @After + fun teardown() { + tile.destroy() + testableLooper.processAllMessages() + } + @Test fun qsTileUi_shouldLookCorrect_whenInactive() { whenever(issueRecordingState.isRecording).thenReturn(false) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt index 7ab8ab93c024..dce110201e1d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt @@ -39,6 +39,9 @@ import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.shared.flag.DualShade +import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -143,6 +146,23 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() { assertThat(underTest.showHeader).isFalse() } + @Test + fun onPanelShapeChanged() = + testScope.runTest { + val actual by + collectLastValue(kosmos.notificationStackAppearanceInteractor.qsPanelShape) + val expected = + ShadeScrimShape( + bounds = ShadeScrimBounds(left = 10f, top = 0f, right = 710f, bottom = 600f), + topRadius = 0, + bottomRadius = 100, + ) + + underTest.onPanelShapeChanged(expected) + + assertThat(expected).isEqualTo(actual) + } + private fun TestScope.lockDevice() { val currentScene by collectLastValue(sceneInteractor.currentScene) kosmos.powerInteractor.setAsleepForTest() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 51f056aa18da..cb7267b2c34c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -1508,10 +1508,8 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test - fun collectFalsingSignals_screenOnAndOff_aodUnavailable() = + fun collectFalsingSignals_screenOnAndOff() = testScope.runTest { - kosmos.fakeKeyguardRepository.setAodAvailable(false) - runCurrent() prepareState( initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.Pin, @@ -1556,53 +1554,6 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test - fun collectFalsingSignals_screenOnAndOff_aodAvailable() = - testScope.runTest { - kosmos.fakeKeyguardRepository.setAodAvailable(true) - runCurrent() - prepareState( - initialSceneKey = Scenes.Lockscreen, - authenticationMethod = AuthenticationMethodModel.Pin, - isDeviceUnlocked = false, - ) - underTest.start() - runCurrent() - verify(falsingCollector, never()).onScreenTurningOn() - verify(falsingCollector, never()).onScreenOnFromTouch() - verify(falsingCollector, never()).onScreenOff() - - powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON) - runCurrent() - verify(falsingCollector, never()).onScreenTurningOn() - verify(falsingCollector, never()).onScreenOnFromTouch() - verify(falsingCollector, never()).onScreenOff() - - powerInteractor.setAsleepForTest() - runCurrent() - verify(falsingCollector, never()).onScreenTurningOn() - verify(falsingCollector, never()).onScreenOnFromTouch() - verify(falsingCollector, never()).onScreenOff() - - powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_TAP) - runCurrent() - verify(falsingCollector, never()).onScreenTurningOn() - verify(falsingCollector, never()).onScreenOnFromTouch() - verify(falsingCollector, never()).onScreenOff() - - powerInteractor.setAsleepForTest() - runCurrent() - verify(falsingCollector, never()).onScreenTurningOn() - verify(falsingCollector, never()).onScreenOnFromTouch() - verify(falsingCollector, never()).onScreenOff() - - powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON) - runCurrent() - verify(falsingCollector, never()).onScreenTurningOn() - verify(falsingCollector, never()).onScreenOnFromTouch() - verify(falsingCollector, never()).onScreenOff() - } - - @Test fun collectFalsingSignals_bouncerVisibility() = testScope.runTest { prepareState( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt index fd9f5f02ee62..20dfd3e11947 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.shade.display import android.view.Display import android.view.Display.TYPE_EXTERNAL +import android.view.MotionEvent import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -28,11 +29,16 @@ import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.shade.domain.interactor.notificationElement +import com.android.systemui.shade.domain.interactor.qsElement +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.test.runTest import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock @SmallTest @RunWith(AndroidJUnit4::class) @@ -50,9 +56,19 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { keyguardRepository, testScope.backgroundScope, shadeOnDefaultDisplayWhenLocked = shadeOnDefaultDisplayWhenLocked, + shadeInteractor = { kosmos.shadeInteractor }, + { kosmos.qsElement }, + { kosmos.notificationElement }, ) } + private fun createMotionEventForDisplay(displayId: Int, xCoordinate: Float = 0f): MotionEvent { + return mock<MotionEvent> { + on { getX() } doReturn xCoordinate + on { getDisplayId() } doReturn displayId + } + } + @Test fun displayId_defaultToDefaultDisplay() { val underTest = createUnderTest() @@ -67,7 +83,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { val displayId by collectLastValue(underTest.displayId) displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL)) - underTest.onStatusBarTouched(2) + underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH) assertThat(displayId).isEqualTo(2) } @@ -79,7 +95,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { val displayIds by collectValues(underTest.displayId) assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY)) - underTest.onStatusBarTouched(2) + underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH) // Never set, as 2 was not a display according to the repository. assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY)) @@ -92,7 +108,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { val displayId by collectLastValue(underTest.displayId) displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL)) - underTest.onStatusBarTouched(2) + underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH) assertThat(displayId).isEqualTo(2) @@ -108,7 +124,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { val displayId by collectLastValue(underTest.displayId) displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL)) - underTest.onStatusBarTouched(2) + underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH) assertThat(displayId).isEqualTo(2) @@ -124,7 +140,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { val displayId by collectLastValue(underTest.displayId) displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL)) - underTest.onStatusBarTouched(2) + underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH) assertThat(displayId).isEqualTo(2) @@ -136,4 +152,48 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { assertThat(displayId).isEqualTo(2) } + + @Test + fun onStatusBarTouched_leftSide_intentSetToNotifications() = + testScope.runTest { + val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true) + + underTest.onStatusBarTouched( + createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.1f), + STATUS_BAR_WIDTH, + ) + + assertThat(underTest.consumeExpansionIntent()).isEqualTo(kosmos.notificationElement) + } + + @Test + fun onStatusBarTouched_rightSide_intentSetToQs() = + testScope.runTest { + val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true) + + underTest.onStatusBarTouched( + createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.95f), + STATUS_BAR_WIDTH, + ) + + assertThat(underTest.consumeExpansionIntent()).isEqualTo(kosmos.qsElement) + } + + @Test + fun onStatusBarTouched_nullAfterConsumed() = + testScope.runTest { + val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true) + + underTest.onStatusBarTouched( + createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.1f), + STATUS_BAR_WIDTH, + ) + assertThat(underTest.consumeExpansionIntent()).isEqualTo(kosmos.notificationElement) + + assertThat(underTest.consumeExpansionIntent()).isNull() + } + + companion object { + private const val STATUS_BAR_WIDTH = 100 + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt index 58396e7cef82..8aa8a50afcd4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt @@ -22,8 +22,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher -import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractorImpl.NotificationElement -import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractorImpl.QSElement import com.android.systemui.shade.shadeTestUtil import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -52,7 +50,7 @@ class ShadeExpandedStateInteractorTest : SysuiTestCase() { val element = currentlyExpandedElement.value - assertThat(element).isInstanceOf(QSElement::class.java) + assertThat(element).isInstanceOf(QSShadeElement::class.java) } @Test @@ -62,7 +60,7 @@ class ShadeExpandedStateInteractorTest : SysuiTestCase() { val element = underTest.currentlyExpandedElement.value - assertThat(element).isInstanceOf(NotificationElement::class.java) + assertThat(element).isInstanceOf(NotificationShadeElement::class.java) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/customization/data/SensorLocationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/customization/data/SensorLocationTest.kt new file mode 100644 index 000000000000..526fd45533c7 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/customization/data/SensorLocationTest.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 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.shared.customization.data + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SensorLocationTest : SysuiTestCase() { + + @Test + fun encodeAndDecode() { + val sensorLocation = SensorLocation(640, 2068, 117, 0.75f) + + assertThat(SensorLocation.decode(sensorLocation.encode())).isEqualTo(sensorLocation) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt index 1df2553d0eb8..c3547bc5cc9b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt @@ -74,7 +74,8 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { testScope.runTest { val radius = MutableStateFlow(32) val leftOffset = MutableStateFlow(0) - val shape by collectLastValue(scrollViewModel.shadeScrimShape(radius, leftOffset)) + val shape by + collectLastValue(scrollViewModel.notificationScrimShape(radius, leftOffset)) // When: receive scrim bounds placeholderViewModel.onScrimBoundsChanged( @@ -87,7 +88,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { bounds = ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f), topRadius = 32, - bottomRadius = 0 + bottomRadius = 0, ) ) @@ -104,7 +105,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { bounds = ShadeScrimBounds(left = 10f, top = 200f, right = 100f, bottom = 550f), topRadius = 24, - bottomRadius = 0 + bottomRadius = 0, ) ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt index 06a2c5af2986..66ccf1822e21 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest @@ -40,28 +41,39 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() { private val underTest = kosmos.notificationStackAppearanceInteractor @Test - fun stackBounds() = + fun stackNotificationScrimBounds() = testScope.runTest { - val stackBounds by collectLastValue(underTest.shadeScrimBounds) + val stackBounds by collectLastValue(underTest.notificationShadeScrimBounds) - val bounds1 = - ShadeScrimBounds( - top = 100f, - bottom = 200f, - ) - underTest.setShadeScrimBounds(bounds1) + val bounds1 = ShadeScrimBounds(top = 100f, bottom = 200f) + underTest.setNotificationShadeScrimBounds(bounds1) assertThat(stackBounds).isEqualTo(bounds1) - val bounds2 = - ShadeScrimBounds( - top = 200f, - bottom = 300f, - ) - underTest.setShadeScrimBounds(bounds2) + val bounds2 = ShadeScrimBounds(top = 200f, bottom = 300f) + underTest.setNotificationShadeScrimBounds(bounds2) assertThat(stackBounds).isEqualTo(bounds2) } @Test + fun setQsPanelShape() = + testScope.runTest { + val actual by collectLastValue(underTest.qsPanelShape) + + val expected1 = + ShadeScrimShape( + bounds = ShadeScrimBounds(top = 0f, bottom = 100f), + topRadius = 0, + bottomRadius = 10, + ) + underTest.setQsPanelShape(expected1) + assertThat(actual).isEqualTo(expected1) + + val expected2 = expected1.copy(topRadius = 10) + underTest.setQsPanelShape(expected2) + assertThat(expected2).isEqualTo(actual) + } + + @Test fun stackRounding() = testScope.runTest { val stackRounding by collectLastValue(underTest.shadeScrimRounding) @@ -76,13 +88,17 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() { } @Test(expected = IllegalStateException::class) - fun setStackBounds_withImproperBounds_throwsException() = + fun stackNotificationScrimBounds_withImproperBounds_throwsException() = testScope.runTest { - underTest.setShadeScrimBounds( - ShadeScrimBounds( - top = 100f, - bottom = 99f, - ) + underTest.setNotificationShadeScrimBounds(ShadeScrimBounds(top = 100f, bottom = 99f)) + } + + @Test(expected = IllegalStateException::class) + fun setQsPanelShape_withImproperBounds_throwsException() = + testScope.runTest { + val invalidBounds = ShadeScrimBounds(top = 0f, bottom = -10f) + underTest.setQsPanelShape( + ShadeScrimShape(bounds = invalidBounds, topRadius = 10, bottomRadius = 10) ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt index 4944c8bd0221..14e7cdc50227 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt @@ -38,12 +38,14 @@ class NotificationsPlaceholderViewModelTest : SysuiTestCase() { private val underTest by lazy { kosmos.notificationsPlaceholderViewModel } @Test - fun onBoundsChanged() = + fun onScrimBoundsChanged() = kosmos.testScope.runTest { val bounds = ShadeScrimBounds(left = 5f, top = 15f, right = 25f, bottom = 35f) underTest.onScrimBoundsChanged(bounds) val stackBounds by - collectLastValue(kosmos.notificationStackAppearanceInteractor.shadeScrimBounds) + collectLastValue( + kosmos.notificationStackAppearanceInteractor.notificationShadeScrimBounds + ) assertThat(stackBounds).isEqualTo(bounds) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt index 7802b921eae0..7c47264d448a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt @@ -36,8 +36,9 @@ import com.android.settingslib.notification.modes.TestModeBuilder import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND import com.android.settingslib.volume.shared.model.AudioStream import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.shared.settings.data.repository.secureSettingsRepository import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository @@ -45,18 +46,13 @@ import com.android.systemui.statusbar.policy.data.repository.fakeZenModeReposito import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import java.time.Duration -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -@OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) @SmallTest class ZenModeInteractorTest : SysuiTestCase() { - private val kosmos = testKosmos() - private val testScope = kosmos.testScope + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val zenModeRepository = kosmos.fakeZenModeRepository private val settingsRepository = kosmos.secureSettingsRepository private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository @@ -65,166 +61,136 @@ class ZenModeInteractorTest : SysuiTestCase() { @Test fun isZenAvailable_off() = - testScope.runTest { + kosmos.runTest { val isZenAvailable by collectLastValue(underTest.isZenAvailable) deviceProvisioningRepository.setDeviceProvisioned(false) - runCurrent() - assertThat(isZenAvailable).isFalse() } @Test fun isZenAvailable_on() = - testScope.runTest { + kosmos.runTest { val isZenAvailable by collectLastValue(underTest.isZenAvailable) deviceProvisioningRepository.setDeviceProvisioned(true) - runCurrent() - assertThat(isZenAvailable).isTrue() } @Test fun isZenModeEnabled_off() = - testScope.runTest { + kosmos.runTest { val enabled by collectLastValue(underTest.isZenModeEnabled) - zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF) - runCurrent() - assertThat(enabled).isFalse() } @Test fun isZenModeEnabled_alarms() = - testScope.runTest { + kosmos.runTest { val enabled by collectLastValue(underTest.isZenModeEnabled) - zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_ALARMS) - runCurrent() - assertThat(enabled).isTrue() } @Test fun isZenModeEnabled_importantInterruptions() = - testScope.runTest { + kosmos.runTest { val enabled by collectLastValue(underTest.isZenModeEnabled) - zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) - runCurrent() - assertThat(enabled).isTrue() } @Test fun isZenModeEnabled_noInterruptions() = - testScope.runTest { + kosmos.runTest { val enabled by collectLastValue(underTest.isZenModeEnabled) - zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) - runCurrent() - assertThat(enabled).isTrue() } @Test fun testIsZenModeEnabled_unknown() = - testScope.runTest { + kosmos.runTest { val enabled by collectLastValue(underTest.isZenModeEnabled) - // this should fail if we ever add another zen mode type zenModeRepository.updateZenMode(4) - runCurrent() - assertThat(enabled).isFalse() } @Test fun areNotificationsHiddenInShade_noPolicy() = - testScope.runTest { + kosmos.runTest { val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) zenModeRepository.updateNotificationPolicy(null) zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) - runCurrent() assertThat(hidden).isFalse() } @Test fun areNotificationsHiddenInShade_zenOffShadeSuppressed() = - testScope.runTest { + kosmos.runTest { val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) zenModeRepository.updateNotificationPolicy( suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST ) zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF) - runCurrent() assertThat(hidden).isFalse() } @Test fun areNotificationsHiddenInShade_zenOnShadeNotSuppressed() = - testScope.runTest { + kosmos.runTest { val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) zenModeRepository.updateNotificationPolicy( suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_STATUS_BAR ) zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) - runCurrent() assertThat(hidden).isFalse() } @Test fun areNotificationsHiddenInShade_zenOnShadeSuppressed() = - testScope.runTest { + kosmos.runTest { val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) zenModeRepository.updateNotificationPolicy( suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST ) zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) - runCurrent() assertThat(hidden).isTrue() } @Test fun shouldAskForZenDuration_falseForNonManualDnd() = - testScope.runTest { + kosmos.runTest { settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_PROMPT) - runCurrent() - assertThat(underTest.shouldAskForZenDuration(TestModeBuilder.EXAMPLE)).isFalse() } @Test fun shouldAskForZenDuration_changesWithSetting() = - testScope.runTest { + kosmos.runTest { val manualDnd = TestModeBuilder().makeManualDnd().setActive(true).build() settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER) - runCurrent() - assertThat(underTest.shouldAskForZenDuration(manualDnd)).isFalse() settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_PROMPT) - runCurrent() - assertThat(underTest.shouldAskForZenDuration(manualDnd)).isTrue() } @Test fun activateMode_nonManualDnd() = - testScope.runTest { + kosmos.runTest { val mode = TestModeBuilder().setActive(false).build() zenModeRepository.addModes(listOf(mode)) settingsRepository.setInt(ZEN_DURATION, 60) - runCurrent() underTest.activateMode(mode) assertThat(zenModeRepository.getMode(mode.id)?.isActive).isTrue() @@ -233,16 +199,14 @@ class ZenModeInteractorTest : SysuiTestCase() { @Test fun activateMode_usesCorrectDuration() = - testScope.runTest { + kosmos.runTest { settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER) - runCurrent() underTest.activateMode(MANUAL_DND) assertThat(zenModeRepository.getModeActiveDuration(MANUAL_DND.id)).isNull() zenModeRepository.deactivateMode(MANUAL_DND) settingsRepository.setInt(ZEN_DURATION, 60) - runCurrent() underTest.activateMode(MANUAL_DND) assertThat(zenModeRepository.getModeActiveDuration(MANUAL_DND.id)) @@ -251,7 +215,7 @@ class ZenModeInteractorTest : SysuiTestCase() { @Test fun deactivateAllModes_updatesCorrectModes() = - testScope.runTest { + kosmos.runTest { zenModeRepository.activateMode(MANUAL_DND) zenModeRepository.addModes( listOf( @@ -267,72 +231,69 @@ class ZenModeInteractorTest : SysuiTestCase() { @Test fun activeModes_computesMainActiveMode() = - testScope.runTest { + kosmos.runTest { val activeModes by collectLastValue(underTest.activeModes) zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME) zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER) - - runCurrent() assertThat(activeModes?.modeNames).hasSize(0) assertThat(activeModes?.mainMode).isNull() zenModeRepository.activateMode("Other") - runCurrent() assertThat(activeModes?.modeNames).containsExactly("Mode Other") assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Other") zenModeRepository.activateMode("Bedtime") - runCurrent() assertThat(activeModes?.modeNames) .containsExactly("Mode Bedtime", "Mode Other") .inOrder() assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Bedtime") zenModeRepository.deactivateMode("Other") - runCurrent() assertThat(activeModes?.modeNames).containsExactly("Mode Bedtime") assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Bedtime") zenModeRepository.deactivateMode("Bedtime") - runCurrent() assertThat(activeModes?.modeNames).hasSize(0) assertThat(activeModes?.mainMode).isNull() } @Test - fun getActiveModes_computesMainActiveMode() = runTest { - zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME) - zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER) - - var activeModes = underTest.getActiveModes() - assertThat(activeModes.modeNames).hasSize(0) - assertThat(activeModes.mainMode).isNull() - - zenModeRepository.activateMode("Other") - activeModes = underTest.getActiveModes() - assertThat(activeModes.modeNames).containsExactly("Mode Other") - assertThat(activeModes.mainMode?.name).isEqualTo("Mode Other") - - zenModeRepository.activateMode("Bedtime") - activeModes = underTest.getActiveModes() - assertThat(activeModes.modeNames).containsExactly("Mode Bedtime", "Mode Other").inOrder() - assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime") - - zenModeRepository.deactivateMode("Other") - activeModes = underTest.getActiveModes() - assertThat(activeModes.modeNames).containsExactly("Mode Bedtime") - assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime") - - zenModeRepository.deactivateMode("Bedtime") - activeModes = underTest.getActiveModes() - assertThat(activeModes.modeNames).hasSize(0) - assertThat(activeModes.mainMode).isNull() - } + fun getActiveModes_computesMainActiveMode() = + kosmos.runTest { + zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME) + zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER) + + var activeModes = underTest.getActiveModes() + assertThat(activeModes.modeNames).hasSize(0) + assertThat(activeModes.mainMode).isNull() + + zenModeRepository.activateMode("Other") + activeModes = underTest.getActiveModes() + assertThat(activeModes.modeNames).containsExactly("Mode Other") + assertThat(activeModes.mainMode?.name).isEqualTo("Mode Other") + + zenModeRepository.activateMode("Bedtime") + activeModes = underTest.getActiveModes() + assertThat(activeModes.modeNames) + .containsExactly("Mode Bedtime", "Mode Other") + .inOrder() + assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime") + + zenModeRepository.deactivateMode("Other") + activeModes = underTest.getActiveModes() + assertThat(activeModes.modeNames).containsExactly("Mode Bedtime") + assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime") + + zenModeRepository.deactivateMode("Bedtime") + activeModes = underTest.getActiveModes() + assertThat(activeModes.modeNames).hasSize(0) + assertThat(activeModes.mainMode).isNull() + } @Test fun mainActiveMode_flows() = - testScope.runTest { + kosmos.runTest { val mainActiveMode by collectLastValue(underTest.mainActiveMode) zenModeRepository.addModes( @@ -355,51 +316,42 @@ class ZenModeInteractorTest : SysuiTestCase() { .build(), ) ) - - runCurrent() assertThat(mainActiveMode).isNull() zenModeRepository.activateMode("Other") - runCurrent() assertThat(mainActiveMode?.name).isEqualTo("Mode Other") assertThat(mainActiveMode?.icon?.key?.resId) .isEqualTo(R.drawable.ic_zen_mode_type_other) zenModeRepository.activateMode("Bedtime") - runCurrent() assertThat(mainActiveMode?.name).isEqualTo("Mode Bedtime") assertThat(mainActiveMode?.icon?.key?.resId) .isEqualTo(R.drawable.ic_zen_mode_type_bedtime) zenModeRepository.deactivateMode("Other") - runCurrent() assertThat(mainActiveMode?.name).isEqualTo("Mode Bedtime") assertThat(mainActiveMode?.icon?.key?.resId) .isEqualTo(R.drawable.ic_zen_mode_type_bedtime) zenModeRepository.deactivateMode("Bedtime") - runCurrent() assertThat(mainActiveMode).isNull() } @Test @EnableFlags(Flags.FLAG_MODES_UI) fun dndMode_flows() = - testScope.runTest { + kosmos.runTest { val dndMode by collectLastValue(underTest.dndMode) - assertThat(dndMode!!.isActive).isFalse() zenModeRepository.activateMode(MANUAL_DND) - runCurrent() - assertThat(dndMode!!.isActive).isTrue() } @Test @EnableFlags(Flags.FLAG_MODES_UI) fun activeModesBlockingMedia_hasModesWithPolicyBlockingMedia() = - testScope.runTest { + kosmos.runTest { val blockingMedia by collectLastValue( underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_MUSIC)) @@ -429,7 +381,6 @@ class ZenModeInteractorTest : SysuiTestCase() { .build(), ) ) - runCurrent() assertThat(blockingMedia!!.mainMode!!.name).isEqualTo("Blocks media, Active") assertThat(blockingMedia!!.modeNames) @@ -440,7 +391,7 @@ class ZenModeInteractorTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_MODES_UI) fun activeModesBlockingAlarms_hasModesWithPolicyBlockingAlarms() = - testScope.runTest { + kosmos.runTest { val blockingAlarms by collectLastValue( underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_ALARM)) @@ -470,7 +421,6 @@ class ZenModeInteractorTest : SysuiTestCase() { .build(), ) ) - runCurrent() assertThat(blockingAlarms!!.mainMode!!.name).isEqualTo("Blocks alarms, Active") assertThat(blockingAlarms!!.modeNames) @@ -481,7 +431,7 @@ class ZenModeInteractorTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_MODES_UI) fun activeModesBlockingAlarms_hasModesWithPolicyBlockingSystem() = - testScope.runTest { + kosmos.runTest { val blockingSystem by collectLastValue( underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_SYSTEM)) @@ -511,7 +461,6 @@ class ZenModeInteractorTest : SysuiTestCase() { .build(), ) ) - runCurrent() assertThat(blockingSystem!!.mainMode!!.name).isEqualTo("Blocks system, Active") assertThat(blockingSystem!!.modeNames) @@ -522,7 +471,7 @@ class ZenModeInteractorTest : SysuiTestCase() { @Test @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API) fun modesHidingNotifications_onlyIncludesModesWithNotifListSuppression() = - testScope.runTest { + kosmos.runTest { val modesHidingNotifications by collectLastValue(underTest.modesHidingNotifications) zenModeRepository.addModes( @@ -554,7 +503,6 @@ class ZenModeInteractorTest : SysuiTestCase() { .build(), ) ) - runCurrent() assertThat(modesHidingNotifications?.map { it.name }) .containsExactly("Has list suppression 1", "Has list suppression 2") diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt new file mode 100644 index 000000000000..4cbe33d66045 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 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.touchpad.tutorial.ui.composable + +import androidx.compose.runtime.saveable.SaverScope +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted +import com.android.systemui.res.R +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class TutorialActionStateSaverTest : SysuiTestCase() { + + private val saver = TutorialActionState.stateSaver() + private val saverScope: SaverScope = + object : SaverScope { + override fun canBeSaved(value: Any) = true + } + + @Test + fun inProgressIsRestoredToNotStartedState() { + assertRestoredState( + savedState = InProgress(progress = 0f), + expectedRestoredState = NotStarted, + ) + } + + @Test + fun inProgressErrorIsRestoredToErrorState() { + assertRestoredState( + savedState = InProgressAfterError(InProgress(progress = 0f)), + expectedRestoredState = Error, + ) + } + + @Test + fun otherStatesAreRestoredToTheSameState() { + assertRestoredState(savedState = NotStarted, expectedRestoredState = NotStarted) + assertRestoredState(savedState = Error, expectedRestoredState = Error) + assertRestoredState( + savedState = Finished(successAnimation = R.raw.trackpad_home_success), + expectedRestoredState = Finished(successAnimation = R.raw.trackpad_home_success), + ) + } + + private fun assertRestoredState( + savedState: TutorialActionState, + expectedRestoredState: TutorialActionState, + ) { + val savedValue = with(saver) { saverScope.save(savedState) } + assertThat(saver.restore(savedValue!!)).isEqualTo(expectedRestoredState) + } +} diff --git a/packages/SystemUI/res/drawable/android16_patch_adaptive.xml b/packages/SystemUI/res/drawable/android16_patch_adaptive.xml new file mode 100644 index 000000000000..277df47438e3 --- /dev/null +++ b/packages/SystemUI/res/drawable/android16_patch_adaptive.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2024 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. +--> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/android16_patch_adaptive_background"/> + <foreground android:drawable="@drawable/android16_patch_adaptive_foreground"/> + <monochrome android:drawable="@drawable/android16_patch_monochrome"/> +</adaptive-icon> diff --git a/packages/SystemUI/res/drawable/android16_patch_adaptive_background.xml b/packages/SystemUI/res/drawable/android16_patch_adaptive_background.xml new file mode 100644 index 000000000000..17c2b927f4fd --- /dev/null +++ b/packages/SystemUI/res/drawable/android16_patch_adaptive_background.xml @@ -0,0 +1,245 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2024 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <group> + <clip-path + android:pathData="M0,0h108v108h-108z"/> + <path + android:pathData="M73,54L54,35L35,54L54,73L73,54Z" + android:fillColor="#34A853"/> + <path + android:pathData="M44.5,44.5L54,44.5L44.5,54L44.5,44.5Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M63.5,63.5L54,63.5L63.5,54L63.5,63.5Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M54,54L54,44.5L63.5,54L54,54Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M54,44.5L54,35L63.5,44.5L54,44.5Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M54,63.5L54,73L44.5,63.5L54,63.5Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M63.5,54L63.5,44.5L73,54L63.5,54Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M44.5,54L44.5,63.5L35,54L44.5,54Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M54,54L54,63.5L44.5,54L54,54Z" + android:fillColor="#1F8E3D"/> + <path + android:pathData="M82.5,25.5L82.5,35L73,25.5L82.5,25.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,44.5L63.5,35L73,44.5L63.5,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,35L82.5,35L73,44.5L73,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M82.5,35L92,35L82.5,44.5L82.5,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,35L54,35L63.5,25.5L63.5,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,44.5L82.5,44.5L73,54L73,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,25.5L63.5,25.5L73,16L73,25.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,35L63.5,35L73,25.5L73,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M82.5,63.5L82.5,73L73,63.5L82.5,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,82.5L63.5,73L73,82.5L63.5,82.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,73L82.5,73L73,82.5L73,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M82.5,73L92,73L82.5,82.5L82.5,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,73L54,73L63.5,63.5L63.5,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,82.5L82.5,82.5L73,92L73,82.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,63.5L63.5,63.5L73,54L73,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M73,73L63.5,73L73,63.5L73,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,63.5L44.5,73L35,63.5L44.5,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M25.5,82.5L25.5,73L35,82.5L25.5,82.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,73L44.5,73L35,82.5L35,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,73L54,73L44.5,82.5L44.5,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M25.5,73L16,73L25.5,63.5L25.5,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,82.5L44.5,82.5L35,92L35,82.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,63.5L25.5,63.5L35,54L35,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,73L25.5,73L35,63.5L35,73Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,25.5L44.5,35L35,25.5L44.5,25.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M25.5,44.5L25.5,35L35,44.5L25.5,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,35L44.5,35L35,44.5L35,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,35L54,35L44.5,44.5L44.5,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M25.5,35L16,35L25.5,25.5L25.5,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,44.5L44.5,44.5L35,54L35,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,25.5L25.5,25.5L35,16L35,25.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M35,35L25.5,35L35,25.5L35,35Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,25.5L54,25.5L63.5,16L63.5,25.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,6.5L54,6.5L44.5,16L44.5,6.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,16L54,25.5L44.5,16L54,16Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,25.5L54,35L44.5,25.5L54,25.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,6.5L54,-3L63.5,6.5L54,6.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,16L44.5,25.5L35,16L44.5,16Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,16L63.5,6.5L73,16L63.5,16Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,16L54,6.5L63.5,16L54,16Z" + android:fillColor="#16161D"/> + <path + android:pathData="M101.5,63.5L92,63.5L101.5,54L101.5,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M82.5,44.5L92,44.5L82.5,54L82.5,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M92,54L92,63.5L82.5,54L92,54Z" + android:fillColor="#16161D"/> + <path + android:pathData="M92,63.5L92,73L82.5,63.5L92,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M92,44.5L92,35L101.5,44.5L92,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M82.5,54L82.5,63.5L73,54L82.5,54Z" + android:fillColor="#16161D"/> + <path + android:pathData="M101.5,54L101.5,44.5L111,54L101.5,54Z" + android:fillColor="#16161D"/> + <path + android:pathData="M92,54L92,44.5L101.5,54L92,54Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,101.5L54,101.5L63.5,92L63.5,101.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,82.5L54,82.5L44.5,92L44.5,82.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,92L54,101.5L44.5,92L54,92Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,101.5L54,111L44.5,101.5L54,101.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,82.5L54,73L63.5,82.5L54,82.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M44.5,92L44.5,101.5L35,92L44.5,92Z" + android:fillColor="#16161D"/> + <path + android:pathData="M63.5,92L63.5,82.5L73,92L63.5,92Z" + android:fillColor="#16161D"/> + <path + android:pathData="M54,92L54,82.5L63.5,92L54,92Z" + android:fillColor="#16161D"/> + <path + android:pathData="M25.5,63.5L16,63.5L25.5,54L25.5,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M6.5,44.5L16,44.5L6.5,54L6.5,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M16,54L16,63.5L6.5,54L16,54Z" + android:fillColor="#16161D"/> + <path + android:pathData="M16,63.5L16,73L6.5,63.5L16,63.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M16,44.5L16,35L25.5,44.5L16,44.5Z" + android:fillColor="#16161D"/> + <path + android:pathData="M6.5,54L6.5,63.5L-3,54L6.5,54Z" + android:fillColor="#16161D"/> + <path + android:pathData="M25.5,54L25.5,44.5L35,54L25.5,54Z" + android:fillColor="#16161D"/> + <path + android:pathData="M16,54L16,44.5L25.5,54L16,54Z" + android:fillColor="#16161D"/> + </group> +</vector> diff --git a/packages/SystemUI/res/drawable/android16_patch_adaptive_foreground.xml b/packages/SystemUI/res/drawable/android16_patch_adaptive_foreground.xml new file mode 100644 index 000000000000..4c2932399c1a --- /dev/null +++ b/packages/SystemUI/res/drawable/android16_patch_adaptive_foreground.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2024 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path + android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z" + android:fillColor="#C6FF00" + android:fillType="evenOdd"/> + <path + android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z" + android:fillColor="#000000"/> + <path + android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.973 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z" + android:fillColor="#000000"/> + <path + android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.557C62.817,58.796 63.205,58.796 63.444,58.557L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z" + android:fillColor="#000000"/> +</vector> diff --git a/packages/SystemUI/res/drawable/android16_patch_monochrome.xml b/packages/SystemUI/res/drawable/android16_patch_monochrome.xml new file mode 100644 index 000000000000..608d5ea6ee48 --- /dev/null +++ b/packages/SystemUI/res/drawable/android16_patch_monochrome.xml @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2024 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path + android:strokeWidth="1" + android:pathData="M54.707,35.707L72.293,53.293A1,1 102.155,0 1,72.293 54.707L54.707,72.293A1,1 0,0 1,53.293 72.293L35.707,54.707A1,1 0,0 1,35.707 53.293L53.293,35.707A1,1 0,0 1,54.707 35.707z" + android:fillColor="#00000000" + android:strokeColor="#ffffff"/> + <path + android:pathData="M55.237,35.177L72.823,52.763A1.75,1.75 67.835,0 1,72.823 55.237L55.237,72.823A1.75,1.75 77.684,0 1,52.763 72.823L35.177,55.237A1.75,1.75 0,0 1,35.177 52.763L52.763,35.177A1.75,1.75 0,0 1,55.237 35.177z" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#ffffff"/> + <path + android:pathData="M44.5,44.5h19v19h-19z" + android:strokeWidth="0.75" + android:fillColor="#00000000" + android:strokeColor="#ffffff"/> + <path + android:pathData="M54,44.5l9.5,9.5l-9.5,9.5l-9.5,-9.5z" + android:strokeWidth="0.75" + android:fillColor="#00000000" + android:strokeColor="#ffffff"/> + <path + android:pathData="M54,35V73" + android:strokeWidth="0.75" + android:fillColor="#00000000" + android:strokeColor="#ffffff"/> + <path + android:pathData="M73,54L35,54" + android:strokeWidth="0.75" + android:fillColor="#00000000" + android:strokeColor="#ffffff"/> + <path + android:pathData="M33.576,31.135l1.146,1.146l-1.146,1.146l-1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M31.146,65.966l1.146,1.146l-1.146,1.146l-1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M26.718,56l1.718,1.718l-1.718,1.718l-1.718,-1.718z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M31.146,48l1.146,1.146l-1.146,1.146l-1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M41.925,34.374l1.718,1.718l-1.718,1.718l-1.718,-1.718z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M63.146,71l1.146,1.146l-1.146,1.146l-1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M48.567,74.553l1.718,1.718l-1.718,1.718l-1.718,-1.718z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M51.146,26l1.146,1.146l-1.146,1.146l-1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M72.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M76.531,36.417l-1.718,1.718l-1.718,-1.718l1.718,-1.718z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M58.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M68.419,36.978l-1.146,1.146l-1.146,-1.146l1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M74.252,64.034l-1.146,1.146l-1.146,-1.146l1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M71.437,76.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M42.984,69.38l-1.146,1.146l-1.146,-1.146l1.146,-1.146z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M82.437,51.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z" + android:fillColor="#E8F5E9"/> + <path + android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z" + android:fillColor="#ffffff"/> + <path + android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.972 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z" + android:fillColor="#ffffff"/> + <path + android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.556C62.817,58.796 63.205,58.796 63.444,58.556L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z" + android:fillColor="#ffffff"/> +</vector> diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml index 6eb7b730e105..c5f468e731f5 100644 --- a/packages/SystemUI/res/layout/volume_dialog_slider.xml +++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml @@ -14,8 +14,8 @@ limitations under the License. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="0dp" - android:layout_height="0dp" + android:layout_width="@dimen/volume_dialog_slider_width" + android:layout_height="match_parent" android:maxHeight="@dimen/volume_dialog_slider_height"> <com.google.android.material.slider.Slider diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java index 1c5da827eeb3..3d2ce4229bd5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java @@ -27,9 +27,10 @@ public interface PluginEnabler { int DISABLED_INVALID_VERSION = 2; int DISABLED_FROM_EXPLICIT_CRASH = 3; int DISABLED_FROM_SYSTEM_CRASH = 4; + int DISABLED_UNKNOWN = 100; @IntDef({ENABLED, DISABLED_MANUALLY, DISABLED_INVALID_VERSION, DISABLED_FROM_EXPLICIT_CRASH, - DISABLED_FROM_SYSTEM_CRASH}) + DISABLED_FROM_SYSTEM_CRASH, DISABLED_UNKNOWN}) @interface DisableReason { } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index ff788484c819..ec97b8a96c1f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -208,7 +208,6 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final InputMethodManager mInputMethodManager; private final DelayableExecutor mMainExecutor; private final Resources mResources; - private final LiftToActivateListener mLiftToActivateListener; private final TelephonyManager mTelephonyManager; private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory; private final FalsingCollector mFalsingCollector; @@ -227,7 +226,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> LatencyTracker latencyTracker, KeyguardMessageAreaController.Factory messageAreaControllerFactory, InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor, - @Main Resources resources, LiftToActivateListener liftToActivateListener, + @Main Resources resources, TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController.Factory emergencyButtonControllerFactory, DevicePostureController devicePostureController, @@ -244,7 +243,6 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mInputMethodManager = inputMethodManager; mMainExecutor = mainExecutor; mResources = resources; - mLiftToActivateListener = liftToActivateListener; mTelephonyManager = telephonyManager; mEmergencyButtonControllerFactory = emergencyButtonControllerFactory; mFalsingCollector = falsingCollector; @@ -284,7 +282,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> return new KeyguardPinViewController((KeyguardPINView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, - mLiftToActivateListener, emergencyButtonController, mFalsingCollector, + emergencyButtonController, mFalsingCollector, mDevicePostureController, mFeatureFlags, mSelectedUserInteractor, mUiEventLogger, mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier); @@ -292,14 +290,14 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, - mLiftToActivateListener, mTelephonyManager, mFalsingCollector, + mTelephonyManager, mFalsingCollector, emergencyButtonController, mFeatureFlags, mSelectedUserInteractor, mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier); } else if (keyguardInputView instanceof KeyguardSimPukView) { return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, - mLiftToActivateListener, mTelephonyManager, mFalsingCollector, + mTelephonyManager, mFalsingCollector, emergencyButtonController, mFeatureFlags, mSelectedUserInteractor, mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier ); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java index e22736b69213..622b67f25da3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java @@ -297,13 +297,10 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils; - android.util.Log.i("KeyguardPINView", "startDisappearAnimation: " + finishRunnable); disappearAnimationUtils.createAnimation( this, 0, 200, mDisappearYTranslation, false, mDisappearAnimationUtils.getInterpolator(), () -> { if (finishRunnable != null) { - android.util.Log.i("KeyguardPINView", - "startDisappearAnimation, invoking run()"); finishRunnable.run(); } }, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index d999994a3312..7f176de547bc 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -34,7 +34,6 @@ import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor; -import com.android.systemui.Flags; import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; @@ -44,7 +43,6 @@ import com.android.systemui.user.domain.interactor.SelectedUserInteractor; public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView> extends KeyguardAbsKeyInputViewController<T> { - private final LiftToActivateListener mLiftToActivateListener; private final FalsingCollector mFalsingCollector; private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor; protected PasswordTextView mPasswordEntry; @@ -73,7 +71,6 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, - LiftToActivateListener liftToActivateListener, EmergencyButtonController emergencyButtonController, FalsingCollector falsingCollector, FeatureFlags featureFlags, @@ -85,7 +82,6 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB messageAreaControllerFactory, latencyTracker, falsingCollector, emergencyButtonController, featureFlags, selectedUserInteractor, bouncerHapticPlayer, userActivityNotifier); - mLiftToActivateListener = liftToActivateListener; mFalsingCollector = falsingCollector; mKeyguardKeyboardInteractor = keyguardKeyboardInteractor; mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); @@ -151,10 +147,6 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB verifyPasswordAndUnlock(); } }); - - if (!Flags.simPinTalkbackFixForDoubleSubmit()) { - okButton.setOnHoverListener(mLiftToActivateListener); - } } if (pinInputFieldStyledFocusState()) { collectFlow(mPasswordEntry, mKeyguardKeyboardInteractor.isAnyKeyboardConnected(), diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index b159a70066ce..9ae4cc6a4b4f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -56,7 +56,7 @@ public class KeyguardPinViewController SecurityMode securityMode, LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, - LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, + LatencyTracker latencyTracker, EmergencyButtonController emergencyButtonController, FalsingCollector falsingCollector, DevicePostureController postureController, FeatureFlags featureFlags, @@ -65,7 +65,7 @@ public class KeyguardPinViewController BouncerHapticPlayer bouncerHapticPlayer, UserActivityNotifier userActivityNotifier) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, - messageAreaControllerFactory, latencyTracker, liftToActivateListener, + messageAreaControllerFactory, latencyTracker, emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier); mKeyguardUpdateMonitor = keyguardUpdateMonitor; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 52c93f72206b..24f77d77dbe1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -91,7 +91,7 @@ public class KeyguardSimPinViewController SecurityMode securityMode, LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, - LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, + LatencyTracker latencyTracker, TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, @@ -99,7 +99,7 @@ public class KeyguardSimPinViewController BouncerHapticPlayer bouncerHapticPlayer, UserActivityNotifier userActivityNotifier) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, - messageAreaControllerFactory, latencyTracker, liftToActivateListener, + messageAreaControllerFactory, latencyTracker, emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier); mKeyguardUpdateMonitor = keyguardUpdateMonitor; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 9adc5bae1ada..e17e8cc05f7e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -89,7 +89,7 @@ public class KeyguardSimPukViewController SecurityMode securityMode, LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, - LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, + LatencyTracker latencyTracker, TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, @@ -97,7 +97,7 @@ public class KeyguardSimPukViewController BouncerHapticPlayer bouncerHapticPlayer, UserActivityNotifier userActivityNotifier) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, - messageAreaControllerFactory, latencyTracker, liftToActivateListener, + messageAreaControllerFactory, latencyTracker, emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier); mKeyguardUpdateMonitor = keyguardUpdateMonitor; diff --git a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java deleted file mode 100644 index 425e50ed6397..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import android.view.MotionEvent; -import android.view.View; -import android.view.accessibility.AccessibilityManager; - -import javax.inject.Inject; - -/** - * Hover listener that implements lift-to-activate interaction for - * accessibility. May be added to multiple views. - */ -class LiftToActivateListener implements View.OnHoverListener { - /** Manager used to query accessibility enabled state. */ - private final AccessibilityManager mAccessibilityManager; - - private boolean mCachedClickableState; - - @Inject - LiftToActivateListener(AccessibilityManager accessibilityManager) { - mAccessibilityManager = accessibilityManager; - } - - @Override - public boolean onHover(View v, MotionEvent event) { - // When touch exploration is turned on, lifting a finger while - // inside the view bounds should perform a click action. - if (mAccessibilityManager.isEnabled() - && mAccessibilityManager.isTouchExplorationEnabled()) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_HOVER_ENTER: - // Lift-to-type temporarily disables double-tap - // activation by setting the view as not clickable. - mCachedClickableState = v.isClickable(); - v.setClickable(false); - break; - case MotionEvent.ACTION_HOVER_EXIT: - final int x = (int) event.getX(); - final int y = (int) event.getY(); - if ((x > v.getPaddingLeft()) && (y > v.getPaddingTop()) - && (x < v.getWidth() - v.getPaddingRight()) - && (y < v.getHeight() - v.getPaddingBottom())) { - v.performClick(); - } - v.setClickable(mCachedClickableState); - break; - } - } - - // Pass the event to View.onHoverEvent() to handle accessibility. - v.onHoverEvent(event); - - // Consume the event so it doesn't fall through to other views. - return true; - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java index 5b433464c1c6..5cba464fc24c 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java @@ -609,15 +609,12 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks final WindowMagnificationController windowMagnificationController = mWindowMagnificationControllerSupplier.get(displayId); if (windowMagnificationController != null) { - boolean isWindowMagnifierActivated = windowMagnificationController.isActivated(); - if (isWindowMagnifierActivated) { - windowMagnificationController.updateDragHandleResourcesIfNeeded(shown); - } + windowMagnificationController.updateDragHandleResourcesIfNeeded(shown); if (shown) { mA11yLogger.logWithPosition( MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED, - isWindowMagnifierActivated + windowMagnificationController.isActivated() ? ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN ); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 08d3e17c03d7..1587ab16fc38 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -1519,12 +1519,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } void updateDragHandleResourcesIfNeeded(boolean settingsPanelIsShown) { + mSettingsPanelVisibility = settingsPanelIsShown; + if (!isActivated()) { return; } - mSettingsPanelVisibility = settingsPanelIsShown; - mDragView.setBackground(mContext.getResources().getDrawable(settingsPanelIsShown ? R.drawable.accessibility_window_magnification_drag_handle_background_change_inset : R.drawable.accessibility_window_magnification_drag_handle_background_inset)); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt index 68ec0f2d57ef..39f55803bb73 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt @@ -68,7 +68,7 @@ interface FingerprintPropertyRepository { val sensorType: StateFlow<FingerprintSensorType> /** The sensor location relative to each physical display. */ - val sensorLocations: Flow<Map<String, SensorLocationInternal>> + val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> } @SysUISingleton @@ -128,12 +128,14 @@ constructor( initialValue = props.value.sensorType.toSensorType(), ) - override val sensorLocations: Flow<Map<String, SensorLocationInternal>> = - props.map { - it.allLocations.associateBy { sensorLocationInternal -> - sensorLocationInternal.displayId - } - } + override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> = + props + .map { props -> props.allLocations.associateBy { it.displayId } } + .stateIn( + applicationScope, + started = SharingStarted.Eagerly, + initialValue = props.value.allLocations.associateBy { it.displayId }, + ) override val propertiesInitialized: Flow<Boolean> = combine( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt index d9ed9ca1f07b..ae855d19715e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt @@ -20,11 +20,11 @@ import android.content.Context import android.graphics.Rect import android.hardware.biometrics.SensorLocationInternal import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository -import com.android.systemui.biometrics.shared.model.SensorLocation import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.shared.customization.data.SensorLocation import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -42,8 +42,8 @@ class FingerprintPropertyInteractor constructor( @Application private val applicationScope: CoroutineScope, @Application private val context: Context, - repository: FingerprintPropertyRepository, - @Main configurationInteractor: ConfigurationInteractor, + private val repository: FingerprintPropertyRepository, + @Main private val configurationInteractor: ConfigurationInteractor, displayStateInteractor: DisplayStateInteractor, udfpsOverlayInteractor: UdfpsOverlayInteractor, ) { @@ -61,11 +61,16 @@ constructor( * Devices with multiple physical displays use unique display ids to determine which sensor is * on the active physical display. This value represents a unique physical display id. */ - private val uniqueDisplayId: Flow<String> = + private val uniqueDisplayId: StateFlow<String> = displayStateInteractor.displayChanges - .map { context.display?.uniqueId } + .map { context.display.uniqueId } .filterNotNull() .distinctUntilChanged() + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = EMPTY_DISPLAY_ID, + ) /** * Sensor location for the: @@ -73,13 +78,15 @@ constructor( * - device's natural screen resolution * - device's natural orientation */ - private val unscaledSensorLocation: Flow<SensorLocationInternal> = - combine(repository.sensorLocations, uniqueDisplayId) { locations, displayId -> + private val unscaledSensorLocation: StateFlow<SensorLocationInternal> = + combineStates(repository.sensorLocations, uniqueDisplayId, applicationScope) { + locations, + displayId -> // Devices without multiple physical displays do not use the display id as the key; // instead, the key is an empty string. locations.getOrDefault( displayId, - locations.getOrDefault("", SensorLocationInternal.DEFAULT), + locations.getOrDefault(EMPTY_DISPLAY_ID, SensorLocationInternal.DEFAULT), ) } @@ -89,18 +96,18 @@ constructor( * - current screen resolution * - device's natural orientation */ - val sensorLocation: Flow<SensorLocation> = - combine(unscaledSensorLocation, configurationInteractor.scaleForResolution) { + val sensorLocation: StateFlow<SensorLocation> = + combineStates( unscaledSensorLocation, - scale -> - val sensorLocation = - SensorLocation( - naturalCenterX = unscaledSensorLocation.sensorLocationX, - naturalCenterY = unscaledSensorLocation.sensorLocationY, - naturalRadius = unscaledSensorLocation.sensorRadius, - scale = scale, - ) - sensorLocation + configurationInteractor.scaleForResolution, + applicationScope, + ) { unscaledSensorLocation, scale -> + SensorLocation( + naturalCenterX = unscaledSensorLocation.sensorLocationX, + naturalCenterY = unscaledSensorLocation.sensorLocationY, + naturalRadius = unscaledSensorLocation.sensorRadius, + scale = scale, + ) } /** @@ -111,4 +118,19 @@ constructor( */ val udfpsSensorBounds: Flow<Rect> = udfpsOverlayInteractor.udfpsOverlayParams.map { it.sensorBounds }.distinctUntilChanged() + + companion object { + + private const val EMPTY_DISPLAY_ID = "" + + /** Combine two state flows to another state flow. */ + private fun <T1, T2, R> combineStates( + flow1: StateFlow<T1>, + flow2: StateFlow<T2>, + scope: CoroutineScope, + transform: (T1, T2) -> R, + ): StateFlow<R> = + combine(flow1, flow2) { v1, v2 -> transform(v1, v2) } + .stateIn(scope, SharingStarted.Eagerly, transform(flow1.value, flow2.value)) + } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt index 22b2888a51a9..a44778657d25 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.bouncer.data.repository +import android.annotation.SuppressLint import android.os.Build import android.util.Log import com.android.keyguard.KeyguardSecurityModel @@ -51,7 +52,11 @@ interface KeyguardBouncerRepository { val primaryBouncerShow: StateFlow<Boolean> val primaryBouncerShowingSoon: StateFlow<Boolean> val primaryBouncerStartingToHide: StateFlow<Boolean> - val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?> + val primaryBouncerStartingDisappearAnimation: MutableSharedFlow<Runnable?> + + fun isPrimaryBouncerStartingDisappearAnimation(): Boolean + + fun isDebuggable(): Boolean /** Determines if we want to instantaneously show the primary bouncer instead of translating. */ val primaryBouncerScrimmed: StateFlow<Boolean> @@ -128,7 +133,7 @@ interface KeyguardBouncerRepository { } @SysUISingleton -class KeyguardBouncerRepositoryImpl +open class KeyguardBouncerRepositoryImpl @Inject constructor( private val clock: SystemClock, @@ -144,9 +149,19 @@ constructor( override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow() private val _primaryBouncerStartingToHide = MutableStateFlow(false) override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow() - private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null) + + @SuppressLint("SharedFlowCreation") override val primaryBouncerStartingDisappearAnimation = - _primaryBouncerDisappearAnimation.asStateFlow() + MutableSharedFlow<Runnable?>(extraBufferCapacity = 2, replay = 1) + + override fun isPrimaryBouncerStartingDisappearAnimation(): Boolean { + val replayCache = primaryBouncerStartingDisappearAnimation.replayCache + return if (!replayCache.isEmpty()) { + replayCache.last() != null + } else { + false + } + } /** Determines if we want to instantaneously show the primary bouncer instead of translating. */ private val _primaryBouncerScrimmed = MutableStateFlow(false) @@ -177,6 +192,7 @@ constructor( _keyguardAuthenticatedPrimaryAuth.asSharedFlow() /** Whether the user requested to show the bouncer when device is already authenticated */ + @SuppressLint("SharedFlowCreation") private val _userRequestedBouncerWhenAlreadyAuthenticated = MutableSharedFlow<Int>() override val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> = _userRequestedBouncerWhenAlreadyAuthenticated.asSharedFlow() @@ -226,7 +242,7 @@ constructor( } override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) { - _primaryBouncerDisappearAnimation.value = runnable + primaryBouncerStartingDisappearAnimation.tryEmit(runnable) } override fun setPanelExpansion(panelExpansion: Float) { @@ -265,9 +281,11 @@ constructor( _lastShownSecurityMode.value = securityMode } + override fun isDebuggable() = Build.IS_DEBUGGABLE + /** Sets up logs for state flows. */ private fun setUpLogging() { - if (!Build.IS_DEBUGGABLE) { + if (!isDebuggable()) { return } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt index 641400a50c89..0c6d7920d7f3 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt @@ -22,6 +22,7 @@ import android.os.Handler import android.os.Trace import android.util.Log import android.view.View +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.DejankUtils @@ -54,7 +55,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map -import com.android.app.tracing.coroutines.launchTraced as launch /** * Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password) @@ -145,7 +145,7 @@ constructor( TAG, "PrimaryBouncerInteractor#show is being called before the " + "primaryBouncerDelegate is set. Let's exit early so we don't " + - "set the wrong primaryBouncer state." + "set the wrong primaryBouncer state.", ) return false } @@ -197,7 +197,7 @@ constructor( if (isFullyShowing()) { SysUiStatsLog.write( SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, - SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN + SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN, ) dismissCallbackRegistry.notifyDismissCancelled() } @@ -223,7 +223,7 @@ constructor( fun setPanelExpansion(expansion: Float) { val oldExpansion = repository.panelExpansionAmount.value val expansionChanged = oldExpansion != expansion - if (repository.primaryBouncerStartingDisappearAnimation.value == null) { + if (!repository.isPrimaryBouncerStartingDisappearAnimation()) { repository.setPanelExpansion(expansion) } @@ -272,7 +272,7 @@ constructor( */ fun setDismissAction( onDismissAction: ActivityStarter.OnDismissAction?, - cancelAction: Runnable? + cancelAction: Runnable?, ) { repository.bouncerDismissActionModel = if (onDismissAction != null && cancelAction != null) { @@ -344,7 +344,7 @@ constructor( fun isFullyShowing(): Boolean { return (repository.primaryBouncerShowingSoon.value || isBouncerShowing()) && repository.panelExpansionAmount.value == KeyguardBouncerConstants.EXPANSION_VISIBLE && - repository.primaryBouncerStartingDisappearAnimation.value == null + !repository.isPrimaryBouncerStartingDisappearAnimation() } /** Returns whether bouncer is scrimmed. */ @@ -361,7 +361,7 @@ constructor( /** Return whether bouncer is animating away. */ fun isAnimatingAway(): Boolean { - return repository.primaryBouncerStartingDisappearAnimation.value != null + return repository.isPrimaryBouncerStartingDisappearAnimation() } /** Return whether bouncer will dismiss with actions */ diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt index d5c815d649c4..434a9ce58c3b 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt @@ -170,8 +170,6 @@ object KeyguardBouncerViewBinder { launch { viewModel.startDisappearAnimation.collect { - android.util.Log.i("KeyguardBouncerViewBinder", - "viewModel.startDisappearAnimation: $it") securityContainerController.startDisappearAnimation(it) } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index 5baec1e025e8..73aaf7f8e460 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -29,11 +29,11 @@ import android.view.KeyEvent.isConfirmKey import android.view.View import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.KeyEventType +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.PinShapeAdapter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor -import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer import com.android.systemui.res.R import dagger.assisted.Assisted @@ -50,7 +50,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.receiveAsFlow -import com.android.app.tracing.coroutines.launchTraced as launch /** Holds UI state and handles user input for the PIN code bouncer UI. */ class PinBouncerViewModel @@ -289,11 +288,10 @@ constructor( * feedback on the view. */ fun onDigitButtonDown(view: View?) { - if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) { - // Current PIN bouncer informs FalsingInteractor#avoidGesture() upon every Pin button - // touch. - super.onDown() - } + // This ends up calling FalsingInteractor#avoidGesture() each time a PIN button is touched. + // It helps make sure that legitimate touch in the PIN bouncer isn't treated as false touch. + super.onDown() + if (bouncerHapticPlayer?.isEnabled == true) { bouncerHapticPlayer.playNumpadKeyFeedback() } else { diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt index b79538aac3e6..60cdccdeaadd 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt @@ -21,8 +21,9 @@ import javax.inject.Inject /** Initializes classes related to falsing. */ @SysUISingleton -class FalsingCoreStartable @Inject constructor(val falsingCollector: FalsingCollector) : - CoreStartable { +class FalsingCoreStartable +@Inject +constructor(@FalsingCollectorActual val falsingCollector: FalsingCollector) : CoreStartable { override fun start() { falsingCollector.init() } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt index 747a2a9bd887..7fcdd9596049 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt @@ -52,7 +52,7 @@ interface ConfigurationRepository { /** Called whenever the configuration has changed. */ val onConfigurationChange: Flow<Unit> - val scaleForResolution: Flow<Float> + val scaleForResolution: StateFlow<Float> val configurationValues: Flow<Configuration> diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt index 97a23e1a5010..4d39b033cd7b 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt @@ -23,6 +23,7 @@ import android.view.Surface import com.android.systemui.common.ui.data.repository.ConfigurationRepository import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map @@ -60,7 +61,7 @@ interface ConfigurationInteractor { val configurationValues: Flow<Configuration> /** Emits the current resolution scaling factor */ - val scaleForResolution: Flow<Float> + val scaleForResolution: StateFlow<Float> /** Given [resourceId], emit the dimension pixel size on config change */ fun dimensionPixelSize(resourceId: Int): Flow<Int> @@ -121,5 +122,5 @@ class ConfigurationInteractorImpl(private val repository: ConfigurationRepositor override val configurationValues: Flow<Configuration> = repository.configurationValues - override val scaleForResolution: Flow<Float> = repository.scaleForResolution + override val scaleForResolution: StateFlow<Float> = repository.scaleForResolution } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt index 6c6d730819f3..911327a0bd18 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt @@ -17,10 +17,10 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor -import com.android.systemui.biometrics.shared.model.SensorLocation import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository +import com.android.systemui.shared.customization.data.SensorLocation import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow diff --git a/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt index 9af259a9b642..d10958a2ce4a 100644 --- a/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt @@ -42,8 +42,8 @@ class ContextualEducationMetricsLogger @Inject constructor() { } SysUiStatsLog.write( SysUiStatsLog.CONTEXTUAL_EDUCATION_TRIGGERED, - statsGestureType, statsEducationType, + statsGestureType, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt index 950a727aedae..0ab5a80e0044 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester @@ -45,7 +46,10 @@ import com.android.systemui.res.R fun ActionKeyTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) { BackHandler(onBack = onBack) val screenConfig = buildScreenConfig() - var actionState: TutorialActionState by remember { mutableStateOf(NotStarted) } + var actionState: TutorialActionState by + rememberSaveable(stateSaver = TutorialActionState.stateSaver()) { + mutableStateOf(NotStarted) + } val focusRequester = remember { FocusRequester() } Box( modifier = diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt index 08e0a9d52faa..21afa40c441b 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt @@ -38,6 +38,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.mapSaver import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester @@ -69,6 +71,31 @@ sealed interface TutorialActionState { data class InProgressAfterError(val inProgress: InProgress) : TutorialActionState, Progress by inProgress + + companion object { + fun stateSaver(): Saver<TutorialActionState, Any> { + val classKey = "class" + val successAnimationKey = "animation" + return mapSaver( + save = { + buildMap { + put(classKey, it::class.java.name) + if (it is Finished) put(successAnimationKey, it.successAnimation) + } + }, + restore = { map -> + when (map[classKey] as? String) { + NotStarted::class.java.name, + InProgress::class.java.name -> NotStarted + Error::class.java.name, + InProgressAfterError::class.java.name -> Error + Finished::class.java.name -> Finished(map[successAnimationKey]!! as Int) + else -> NotStarted + } + }, + ) + } + } } interface Progress { diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt index b0816ce608a0..dc0d7b689d0a 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt @@ -131,10 +131,14 @@ private fun SuccessAnimation( ) { val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(finishedState.successAnimation)) + var animationFinished by rememberSaveable(key = "animationFinished") { mutableStateOf(false) } val progress by animateLottieCompositionAsState(composition, iterations = 1) + if (progress == 1f) { + animationFinished = true + } LottieAnimation( composition = composition, - progress = { progress }, + progress = { if (animationFinished) 1f else progress }, dynamicProperties = animationProperties, modifier = Modifier.fillMaxSize(), ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt index 7a72732ea6bf..18cabad6b2d5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt @@ -33,6 +33,7 @@ import android.util.Log import com.android.app.tracing.coroutines.runBlockingTraced as runBlocking import com.android.systemui.SystemUIAppComponentFactoryBase import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback +import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager @@ -46,6 +47,7 @@ class CustomizationProvider : @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor @Inject lateinit var shadeModeInteractor: ShadeModeInteractor + @Inject lateinit var fingerprintPropertyInteractor: FingerprintPropertyInteractor @Inject lateinit var previewManager: KeyguardRemotePreviewManager @Inject @Main lateinit var mainDispatcher: CoroutineDispatcher @@ -345,6 +347,14 @@ class CustomizationProvider : } private fun queryRuntimeValues(): Cursor { + // If not UDFPS, the udfpsLocation will be null + val udfpsLocation = + if (fingerprintPropertyInteractor.isUdfps.value) { + fingerprintPropertyInteractor.sensorLocation.value + } else { + null + } + return MatrixCursor( arrayOf( Contract.RuntimeValuesTable.Columns.NAME, @@ -358,6 +368,9 @@ class CustomizationProvider : if (shadeModeInteractor.isShadeLayoutWide.value) 1 else 0, ) ) + addRow( + arrayOf(Contract.RuntimeValuesTable.KEY_UDFPS_LOCATION, udfpsLocation?.encode()) + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 647362873015..5baef915ea01 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -3492,7 +3492,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void showSurfaceBehindKeyguard() { mSurfaceBehindRemoteAnimationRequested = true; - if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { + if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS && !KeyguardWmStateRefactor.isEnabled()) { startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */); return; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt index f5e0c81ca9a2..b1a2ec92401a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -20,7 +20,6 @@ import android.animation.FloatEvaluator import android.animation.IntEvaluator import com.android.keyguard.KeyguardViewController import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor -import com.android.systemui.biometrics.shared.model.SensorLocation import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor @@ -34,6 +33,7 @@ import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shared.customization.data.SensorLocation import com.android.systemui.util.kotlin.sample import dagger.Lazy import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt index 8fe225a8b93f..88fdc83fa7a0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt @@ -100,7 +100,7 @@ constructor( keyguardClockInteractor.clockShouldBeCentered.stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = false, + initialValue = true, ) // To translate elements below smartspace in weather clock to avoid overlapping between date diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java index 40f59744e038..7f1c7a525f97 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java @@ -18,6 +18,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.shared.plugins.PluginEnabler; @@ -28,6 +29,7 @@ import javax.inject.Singleton; /** */ @Singleton public class PluginEnablerImpl implements PluginEnabler { + private static final String TAG = "PluginEnablerImpl"; private static final String CRASH_DISABLED_PLUGINS_PREF_FILE = "auto_disabled_plugins_prefs"; private final PackageManager mPm; @@ -64,8 +66,13 @@ public class PluginEnablerImpl implements PluginEnabler { @Override public boolean isEnabled(ComponentName component) { - return mPm.getComponentEnabledSetting(component) - != PackageManager.COMPONENT_ENABLED_STATE_DISABLED; + try { + return mPm.getComponentEnabledSetting(component) + != PackageManager.COMPONENT_ENABLED_STATE_DISABLED; + } catch (IllegalArgumentException ex) { + Log.e(TAG, "Package Manager Exception", ex); + return false; + } } @Override @@ -73,6 +80,6 @@ public class PluginEnablerImpl implements PluginEnabler { if (isEnabled(componentName)) { return ENABLED; } - return mAutoDisabledPrefs.getInt(componentName.flattenToString(), DISABLED_MANUALLY); + return mAutoDisabledPrefs.getInt(componentName.flattenToString(), DISABLED_UNKNOWN); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt index c0c0aea073f1..465d08b36ed0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt @@ -17,13 +17,14 @@ package com.android.systemui.qs.ui.viewmodel import androidx.compose.runtime.getValue -import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel +import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.awaitCancellation @@ -44,6 +45,7 @@ class QuickSettingsShadeOverlayContentViewModel constructor( val shadeInteractor: ShadeInteractor, val sceneInteractor: SceneInteractor, + val notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor, val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory, quickSettingsContainerViewModelFactory: QuickSettingsContainerViewModel.Factory, ) : ExclusiveActivatable() { @@ -92,6 +94,11 @@ constructor( awaitCancellation() } + /** Notifies that the bounds of the QuickSettings panel have changed. */ + fun onPanelShapeChanged(shape: ShadeScrimShape?) { + notificationStackAppearanceInteractor.setQsPanelShape(shape) + } + fun onScrimClicked() { shadeInteractor.collapseQuickSettingsShade(loggingReason = "shade scrim clicked") } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 3a23a71cf7bf..8657c1723507 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -94,7 +94,6 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChangedBy -import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNot @@ -717,14 +716,7 @@ constructor( } applicationScope.launch { - keyguardInteractor.isAodAvailable - .flatMapLatest { isAodAvailable -> - if (!isAodAvailable) { - powerInteractor.detailedWakefulness - } else { - emptyFlow() - } - } + powerInteractor.detailedWakefulness .distinctUntilChangedBy { it.isAwake() } .collect { wakefulness -> when { diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS index 5b8277284117..89454b84a528 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS @@ -1,5 +1,7 @@ justinweir@google.com syeonlee@google.com +nicomazz@google.com +burakov@google.com per-file *Notification* = set noparent per-file *Notification* = file:../statusbar/notification/OWNERS @@ -9,13 +11,13 @@ per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google per-file *ShadeHeader* = syeonlee@google.com, kozynski@google.com, asc@google.com per-file *Interactor* = set noparent -per-file *Interactor* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com +per-file *Interactor* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com, nicomazz@google.com, burakov@google.com per-file *Repository* = set noparent -per-file *Repository* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com +per-file *Repository* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com, nicomazz@google.com, burakov@google.com -per-file NotificationShadeWindow* = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com +per-file NotificationShadeWindow* = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com, nicomazz@google.com, burakov@google.com per-file NotificationPanelUnfoldAnimationController.kt = alexflo@google.com, jeffdq@google.com, juliacr@google.com -per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com -per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com +per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com, nicomazz@google.com, burakov@google.com +per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com, nicomazz@google.com, burakov@google.com diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt index e358dcec8b10..ec9bba7c1f0f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt @@ -95,7 +95,7 @@ constructor( */ @Synchronized fun onShadeDisplayChanging(displayId: Int) { - previousJob?.cancel(CancellationException("New shade move in progress")) + previousJob?.cancel(CancellationException("New shade move in progress to $displayId")) previousJob = bgScope.launch { onShadeDisplayChangingAsync(displayId) } } @@ -109,8 +109,8 @@ constructor( val reason = when (e) { is CancellationException -> - "Shade move cancelled as a new move is being done " + - "before the previous one finished." + "Shade move to $displayId cancelled as a new move is being done " + + "before the previous one finished. Message: ${e.message}" else -> "Shade move cancelled." } diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt index 17b5e5b584b4..d53f9f7ec595 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade.display +import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor.ShadeElement import dagger.Binds import dagger.Module import dagger.multibindings.IntoSet @@ -33,11 +34,33 @@ interface ShadeDisplayPolicy { val displayId: StateFlow<Int> } +/** Return the latest element the user intended to expand in the shade (notifications or QS). */ +interface ShadeExpansionIntent { + /** + * Returns the latest element the user intended to expand in the shade (notifications or QS). + * + * When the shade moves to a different display (e.g., due to a touch on the status bar of an + * external display), it's first collapsed and then re-expanded on the target display. + * + * If the user was trying to open a specific element (QS or notifications) when the shade was on + * the original display, that intention might be lost during the collapse/re-expand transition. + * This is used to preserve the user's intention, ensuring the correct element is expanded on + * the target display. + * + * Note that the expansion intent is kept for a very short amount of time (ideally, just a bit + * above the time it takes for the shade to collapse) + */ + fun consumeExpansionIntent(): ShadeElement? +} + @Module interface ShadeDisplayPolicyModule { @Binds fun provideDefaultPolicy(impl: StatusBarTouchShadeDisplayPolicy): ShadeDisplayPolicy + @Binds + fun provideShadeExpansionIntent(impl: StatusBarTouchShadeDisplayPolicy): ShadeExpansionIntent + @IntoSet @Binds fun provideDefaultDisplayPolicyToSet(impl: DefaultDisplayShadePolicy): ShadeDisplayPolicy diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt index 30b086f03d66..91020aa7bdb0 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt @@ -18,16 +18,25 @@ package com.android.systemui.shade.display import android.util.Log import android.view.Display +import android.view.MotionEvent import com.android.app.tracing.coroutines.launchTraced import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DisplayRepository import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.shade.ShadeOnDefaultDisplayWhenLocked +import com.android.systemui.shade.domain.interactor.NotificationShadeElement +import com.android.systemui.shade.domain.interactor.QSShadeElement +import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor.ShadeElement +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround +import dagger.Lazy +import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -49,14 +58,20 @@ class StatusBarTouchShadeDisplayPolicy constructor( displayRepository: DisplayRepository, keyguardRepository: KeyguardRepository, - @Background val backgroundScope: CoroutineScope, - @ShadeOnDefaultDisplayWhenLocked val shadeOnDefaultDisplayWhenLocked: Boolean, -) : ShadeDisplayPolicy { + @Background private val backgroundScope: CoroutineScope, + @ShadeOnDefaultDisplayWhenLocked private val shadeOnDefaultDisplayWhenLocked: Boolean, + private val shadeInteractor: Lazy<ShadeInteractor>, + private val qsShadeElement: Lazy<QSShadeElement>, + private val notificationElement: Lazy<NotificationShadeElement>, +) : ShadeDisplayPolicy, ShadeExpansionIntent { override val name: String = "status_bar_latest_touch" private val currentDisplayId = MutableStateFlow(Display.DEFAULT_DISPLAY) private val availableDisplayIds: StateFlow<Set<Int>> = displayRepository.displayIds + private var latestIntent = AtomicReference<ShadeElement?>() + private var timeoutJob: Job? = null + override val displayId: StateFlow<Int> = if (shadeOnDefaultDisplayWhenLocked) { keyguardRepository.isKeyguardShowing @@ -75,8 +90,29 @@ constructor( private var removalListener: Job? = null /** Called when the status bar on the given display is touched. */ - fun onStatusBarTouched(statusBarDisplayId: Int) { + fun onStatusBarTouched(event: MotionEvent, statusBarWidth: Int) { ShadeWindowGoesAround.isUnexpectedlyInLegacyMode() + updateShadeDisplayIfNeeded(event) + updateExpansionIntent(event, statusBarWidth) + } + + override fun consumeExpansionIntent(): ShadeElement? { + return latestIntent.getAndSet(null) + } + + private fun updateExpansionIntent(event: MotionEvent, statusBarWidth: Int) { + val element = classifyStatusBarEvent(event, statusBarWidth) + latestIntent.set(element) + timeoutJob?.cancel() + timeoutJob = + backgroundScope.launchTraced("StatusBarTouchDisplayPolicy#intentTimeout") { + delay(EXPANSION_INTENT_EXPIRY) + latestIntent.set(null) + } + } + + private fun updateShadeDisplayIfNeeded(event: MotionEvent) { + val statusBarDisplayId = event.displayId if (statusBarDisplayId !in availableDisplayIds.value) { Log.e(TAG, "Got touch on unknown display $statusBarDisplayId") return @@ -90,6 +126,17 @@ constructor( } } + private fun classifyStatusBarEvent( + motionEvent: MotionEvent, + statusbarWidth: Int, + ): ShadeElement { + val xPercentage = motionEvent.x / statusbarWidth + val threshold = shadeInteractor.get().getTopEdgeSplitFraction() + return if (xPercentage < threshold) { + notificationElement.get() + } else qsShadeElement.get() + } + private fun monitorDisplayRemovals(): Job { return backgroundScope.launchTraced("StatusBarTouchDisplayPolicy#monitorDisplayRemovals") { currentDisplayId.subscriptionCount @@ -112,5 +159,6 @@ constructor( private companion object { const val TAG = "StatusBarTouchDisplayPolicy" + val EXPANSION_INTENT_EXPIRY = 2.seconds } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt index 691a383cb338..f67d33122063 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt @@ -30,6 +30,7 @@ import com.android.systemui.shade.ShadeDisplayChangeLatencyTracker import com.android.systemui.shade.ShadeTraceLogger.logMoveShadeWindowTo import com.android.systemui.shade.ShadeTraceLogger.traceReparenting import com.android.systemui.shade.data.repository.ShadeDisplaysRepository +import com.android.systemui.shade.display.ShadeExpansionIntent import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround import com.android.window.flags.Flags import java.util.Optional @@ -49,6 +50,7 @@ constructor( @Main private val mainThreadContext: CoroutineContext, private val shadeDisplayChangeLatencyTracker: ShadeDisplayChangeLatencyTracker, shadeExpandedInteractor: Optional<ShadeExpandedStateInteractor>, + private val shadeExpansionIntent: ShadeExpansionIntent, ) : CoreStartable { private val shadeExpandedInteractor = @@ -90,10 +92,7 @@ constructor( withContext(mainThreadContext) { traceReparenting { shadeDisplayChangeLatencyTracker.onShadeDisplayChanging(destinationId) - val expandedElement = shadeExpandedInteractor.currentlyExpandedElement.value - expandedElement?.collapse(reason = "Shade window move") - reparentToDisplayId(id = destinationId) - expandedElement?.expand(reason = "Shade window move") + collapseAndExpandShadeIfNeeded { reparentToDisplayId(id = destinationId) } checkContextDisplayMatchesExpected(destinationId) } } @@ -106,6 +105,18 @@ constructor( } } + private suspend fun collapseAndExpandShadeIfNeeded(wrapped: () -> Unit) { + val previouslyExpandedElement = shadeExpandedInteractor.currentlyExpandedElement.value + previouslyExpandedElement?.collapse(reason = COLLAPSE_EXPAND_REASON) + + wrapped() + + // If the user was trying to expand a specific shade element, let's make sure to expand + // that one. Otherwise, we can just re-expand the previous expanded element. + shadeExpansionIntent.consumeExpansionIntent()?.expand(COLLAPSE_EXPAND_REASON) + ?: previouslyExpandedElement?.expand(reason = COLLAPSE_EXPAND_REASON) + } + private fun checkContextDisplayMatchesExpected(destinationId: Int) { if (shadeContext.displayId != destinationId) { Log.wtf( @@ -125,5 +136,6 @@ constructor( private companion object { const val TAG = "ShadeDisplaysInteractor" + const val COLLAPSE_EXPAND_REASON = "Shade window move" } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt index dd3abeec5a72..aba5a6bceb10 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade.domain.interactor +import android.util.Log import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -24,6 +25,8 @@ import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.util.kotlin.Utils.Companion.combineState import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -31,7 +34,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.withTimeoutOrNull /** * Wrapper around [ShadeInteractor] to facilitate expansion and collapse of Notifications and quick @@ -47,7 +50,7 @@ interface ShadeExpandedStateInteractor { val currentlyExpandedElement: StateFlow<ShadeElement?> /** An element from the shade window that can be expanded or collapsed. */ - abstract class ShadeElement { + sealed class ShadeElement { /** Expands the shade element, returning when the expansion is done */ abstract suspend fun expand(reason: String) @@ -56,17 +59,18 @@ interface ShadeExpandedStateInteractor { } } +private val EXPAND_COLLAPSE_TIMEOUT: Duration = 1.seconds + @SysUISingleton class ShadeExpandedStateInteractorImpl @Inject constructor( - private val shadeInteractor: ShadeInteractor, + shadeInteractor: ShadeInteractor, @Background private val bgScope: CoroutineScope, + private val notificationElement: NotificationShadeElement, + private val qsElement: QSShadeElement, ) : ShadeExpandedStateInteractor { - private val notificationElement = NotificationElement() - private val qsElement = QSElement() - override val currentlyExpandedElement: StateFlow<ShadeElement?> = if (SceneContainerFlag.isEnabled) { combineState( @@ -84,35 +88,54 @@ constructor( } else { MutableStateFlow(null) } +} - inner class NotificationElement : ShadeElement() { - override suspend fun expand(reason: String) { - shadeInteractor.expandNotificationsShade(reason) - shadeInteractor.shadeExpansion.waitUntil(1f) +private suspend fun StateFlow<Float>.waitUntil(f: Float, coroutineContext: CoroutineContext) { + // it's important to not do this in the main thread otherwise it will block any rendering. + withContext(coroutineContext) { + withTimeoutOrNull(EXPAND_COLLAPSE_TIMEOUT) { + traceWaitForExpansion(expansion = f) { first { it == f } } } + ?: Log.e( + "ShadeExpStateInteractor", + "Timed out after ${EXPAND_COLLAPSE_TIMEOUT.inWholeMilliseconds}ms while waiting " + + "for expansion to match $f. Current one: $value", + ) + } +} - override suspend fun collapse(reason: String) { - shadeInteractor.collapseNotificationsShade(reason) - shadeInteractor.shadeExpansion.waitUntil(0f) - } +@SysUISingleton +class NotificationShadeElement +@Inject +constructor( + private val shadeInteractor: ShadeInteractor, + @Background private val bgContext: CoroutineContext, +) : ShadeElement() { + override suspend fun expand(reason: String) { + shadeInteractor.expandNotificationsShade(reason) + shadeInteractor.shadeExpansion.waitUntil(1f, bgContext) } - inner class QSElement : ShadeElement() { - override suspend fun expand(reason: String) { - shadeInteractor.expandQuickSettingsShade(reason) - shadeInteractor.qsExpansion.waitUntil(1f) - } + override suspend fun collapse(reason: String) { + shadeInteractor.collapseNotificationsShade(reason) + shadeInteractor.shadeExpansion.waitUntil(0f, bgContext) + } +} - override suspend fun collapse(reason: String) { - shadeInteractor.collapseQuickSettingsShade(reason) - shadeInteractor.qsExpansion.waitUntil(0f) - } +@SysUISingleton +class QSShadeElement +@Inject +constructor( + private val shadeInteractor: ShadeInteractor, + @Background private val bgContext: CoroutineContext, +) : ShadeElement() { + override suspend fun expand(reason: String) { + shadeInteractor.expandQuickSettingsShade(reason) + shadeInteractor.qsExpansion.waitUntil(1f, bgContext) } - private suspend fun StateFlow<Float>.waitUntil(f: Float) { - // it's important to not do this in the main thread otherwise it will block any rendering. - withContext(bgScope.coroutineContext) { - withTimeout(1.seconds) { traceWaitForExpansion(expansion = f) { first { it == f } } } - } + override suspend fun collapse(reason: String) { + shadeInteractor.collapseQuickSettingsShade(reason) + shadeInteractor.qsExpansion.waitUntil(0f, bgContext) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java index 5ef5a7d2139c..af51c49204bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java @@ -284,6 +284,7 @@ public class ImmersiveModeConfirmation implements CoreStartable, CommandQueue.Ca | WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY | WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW; lp.setTitle("ImmersiveModeConfirmation"); + lp.accessibilityTitle = mSysUiContext.getString(R.string.immersive_cling_title); lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation; lp.token = getWindowToken(); return lp; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index ea48fb4ffd92..086c32cbae5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -60,7 +60,6 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule; import com.android.systemui.statusbar.notification.domain.NotificationDomainLayerModule; import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor; -import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule; import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.icon.ConversationIconManager; import com.android.systemui.statusbar.notification.icon.IconManager; @@ -109,7 +108,6 @@ import javax.inject.Provider; * Dagger Module for classes found within the com.android.systemui.statusbar.notification package. */ @Module(includes = { - FooterViewModelModule.class, KeyguardNotificationVisibilityProviderModule.class, NotificationDataLayerModule.class, NotificationDomainLayerModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index a670f69df601..c88dd7af6b24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -40,6 +40,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter; import com.android.systemui.statusbar.notification.row.FooterViewButton; @@ -333,11 +334,9 @@ public class FooterView extends StackScrollerDecorView { /** * Whether the touch is outside the Clear all button. - * - * TODO(b/293167744): This is an artifact from the time when we could press underneath the - * shade to dismiss it. Check if it's safe to remove. */ public boolean isOnEmptySpace(float touchX, float touchY) { + SceneContainerFlag.assertInLegacyMode(); return touchX < mContent.getX() || touchX > mContent.getX() + mContent.getWidth() || touchY < mContent.getY() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt index 5696e9f0c5a2..c895c41960d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.footer.ui.viewmodel import android.content.Intent import android.provider.Settings import com.android.internal.jank.InteractionJankMonitor -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor @@ -32,10 +31,8 @@ import com.android.systemui.util.kotlin.sample import com.android.systemui.util.ui.AnimatableEvent import com.android.systemui.util.ui.AnimatedValue import com.android.systemui.util.ui.toAnimatedValueFlow -import dagger.Module -import dagger.Provides -import java.util.Optional -import javax.inject.Provider +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -44,7 +41,9 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart /** ViewModel for [FooterView]. */ -class FooterViewModel( +class FooterViewModel +@AssistedInject +constructor( activeNotificationsInteractor: ActiveNotificationsInteractor, notificationSettingsInteractor: NotificationSettingsInteractor, seenNotificationsInteractor: SeenNotificationsInteractor, @@ -141,25 +140,9 @@ class FooterViewModel( AnimatedValue.NotAnimating(!messageVisible) }, ) -} -// TODO: b/293167744 - remove this, use new viewmodel style -@Module -object FooterViewModelModule { - @Provides - @SysUISingleton - fun provideOptional( - activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>, - notificationSettingsInteractor: Provider<NotificationSettingsInteractor>, - seenNotificationsInteractor: Provider<SeenNotificationsInteractor>, - shadeInteractor: Provider<ShadeInteractor>, - ): Optional<FooterViewModel> = - Optional.of( - FooterViewModel( - activeNotificationsInteractor.get(), - notificationSettingsInteractor.get(), - seenNotificationsInteractor.get(), - shadeInteractor.get(), - ) - ) + @AssistedFactory + interface Factory { + fun create(): FooterViewModel + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 70e27a981b49..7b3a4710b69c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -504,6 +504,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder CharSequence redactedMessage = systemUiContext.getString( R.string.redacted_notification_single_line_text ); + redacted.setWhen(original.getWhen()); if (originalStyle instanceof MessagingStyle oldStyle) { MessagingStyle newStyle = new MessagingStyle(oldStyle.getUser()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt index 98d704c75d33..f3ee34f45a50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row.icon import android.annotation.WorkerThread import android.app.ActivityManager import android.app.Flags +import android.app.Flags.notificationsRedesignThemedAppIcons import android.content.Context import android.content.pm.PackageManager.NameNotFoundException import android.graphics.Color @@ -29,6 +30,8 @@ import android.util.Log import com.android.internal.R import com.android.launcher3.icons.BaseIconFactory import com.android.launcher3.icons.BaseIconFactory.IconOptions +import com.android.launcher3.icons.BitmapInfo +import com.android.launcher3.icons.mono.MonoIconThemeController import com.android.launcher3.util.UserIconInfo import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton @@ -55,6 +58,7 @@ interface AppIconProvider { packageName: String, context: Context, withWorkProfileBadge: Boolean = false, + themed: Boolean = true, ): Drawable /** @@ -74,6 +78,17 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D dumpManager.registerNormalDumpable(TAG, this) } + private class NotificationIcons(context: Context?, fillResIconDpi: Int, iconBitmapSize: Int) : + BaseIconFactory(context, fillResIconDpi, iconBitmapSize) { + + init { + if (notificationsRedesignThemedAppIcons()) { + // Initialize the controller so that we can support themed icons. + mThemeController = MonoIconThemeController() + } + } + } + private val iconFactory: BaseIconFactory get() { val isLowRam = ActivityManager.isLowRamDeviceStatic() @@ -83,7 +98,7 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D if (isLowRam) R.dimen.notification_small_icon_size_low_ram else R.dimen.notification_small_icon_size ) - return BaseIconFactory(sysuiContext, res.configuration.densityDpi, iconSize) + return NotificationIcons(sysuiContext, res.configuration.densityDpi, iconSize) } private val cache = NotifCollectionCache<Drawable>() @@ -92,12 +107,15 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D packageName: String, context: Context, withWorkProfileBadge: Boolean, + themed: Boolean, ): Drawable { // Add a suffix to distinguish the app installed on the work profile, since the icon will // be different. val key = packageName + if (withWorkProfileBadge) WORK_SUFFIX else "" - return cache.getOrFetch(key) { fetchAppIcon(packageName, context, withWorkProfileBadge) } + return cache.getOrFetch(key) { + fetchAppIcon(packageName, context, withWorkProfileBadge, themed) + } } @WorkerThread @@ -105,6 +123,7 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D packageName: String, context: Context, withWorkProfileBadge: Boolean, + themed: Boolean, ): Drawable { val pm = context.packageManager val icon = pm.getApplicationInfo(packageName, 0).loadUnbadgedIcon(pm) @@ -113,13 +132,14 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D IconOptions().apply { setUser(userIconInfo(context, withWorkProfileBadge)) setBitmapGenerationMode(BaseIconFactory.MODE_HARDWARE) - // This color is not used since we're not showing the themed icons. We're just - // setting it so that the icon factory doesn't try to extract colors from our bitmap - // (since it won't work, given it's a hardware bitmap). + // This color will not be used, but we're just setting it so that the icon factory + // doesn't try to extract colors from our bitmap (since it won't work, given it's a + // hardware bitmap). setExtractedColor(Color.BLUE) } val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options) - return badgedIcon.newIcon(sysuiContext) + val creationFlags = if (themed) BitmapInfo.FLAG_THEMED else 0 + return badgedIcon.newIcon(sysuiContext, creationFlags) } private fun userIconInfo(context: Context, withWorkProfileBadge: Boolean): UserIconInfo { @@ -165,6 +185,7 @@ class NoOpIconProvider : AppIconProvider { packageName: String, context: Context, withWorkProfileBadge: Boolean, + themed: Boolean, ): Drawable { Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.") return ColorDrawable(Color.WHITE) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt index 64fdf6fc2708..bb4aa86fc6a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row.icon +import android.app.Flags.notificationsRedesignThemedAppIcons import android.content.Context import android.graphics.drawable.Drawable import android.util.AttributeSet @@ -74,6 +75,7 @@ constructor( sbn.packageName, context, withWorkProfileBadge, + themed = notificationsRedesignThemedAppIcons(), ) } } 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 7b55e83a0a99..a8a1318664f2 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 @@ -512,23 +512,27 @@ public class NotificationStackScrollLayout */ private final Path mRoundedClipPath = new Path(); + /** The clip path defining where we are NOT allowed to draw. */ + private final Path mNegativeRoundedClipPath = new Path(); + /** * The clip Path used to clip the launching notification. This may be different * from the normal path, as the views launch animation could start clipped. */ private final Path mLaunchedNotificationClipPath = new Path(); - /** - * Should we use rounded rect clipping right now - */ + /** Should we use rounded rect clipping right now */ private boolean mShouldUseRoundedRectClipping = false; + /** Should we set an out path for the drawing canvas */ + private boolean mShouldUseNegativeRoundedRectClipping = false; + private int mRoundedRectClippingLeft; private int mRoundedRectClippingTop; private int mRoundedRectClippingBottom; private int mRoundedRectClippingRight; private int mRoundedRectClippingYTranslation; - private final float[] mBgCornerRadii = new float[8]; + private final float[] mRoundedClipCornerRadii = new float[8]; /** * Whether stackY should be animated in case the view is getting shorter than the scroll @@ -3864,7 +3868,7 @@ public class NotificationStackScrollLayout if (!SceneContainerFlag.isEnabled()) { return !isInsideQsHeader(ev); } - ShadeScrimShape shape = mScrollViewFields.getScrimClippingShape(); + ShadeScrimShape shape = mScrollViewFields.getClippingShape(); if (shape == null) { return true; // When there is no scrim, consider this event scrollable. } @@ -5390,7 +5394,8 @@ public class NotificationStackScrollLayout println(pw, "pulsing", mPulsing); println(pw, "expanded", mIsExpanded); println(pw, "headsUpPinned", mInHeadsUpPinnedMode); - println(pw, "qsClipping", mShouldUseRoundedRectClipping); + println(pw, "roundedRectClipping", mShouldUseRoundedRectClipping); + println(pw, "negativeRoundedRectClipping", mShouldUseNegativeRoundedRectClipping); println(pw, "qsClipDismiss", mDismissUsingRowTranslationX); println(pw, "visibility", visibilityString(getVisibility())); println(pw, "alpha", getAlpha()); @@ -5469,8 +5474,8 @@ public class NotificationStackScrollLayout pw.append(" r=").print(mRoundedRectClippingRight); pw.append(" b=").print(mRoundedRectClippingBottom); pw.append(" +y=").print(mRoundedRectClippingYTranslation); - pw.append("} topRadius=").print(mBgCornerRadii[0]); - pw.append(" bottomRadius=").println(mBgCornerRadii[4]); + pw.append("} topRadius=").print(mRoundedClipCornerRadii[0]); + pw.append(" bottomRadius=").println(mRoundedClipCornerRadii[4]); } public boolean isFullyHidden() { @@ -5917,14 +5922,12 @@ public class NotificationStackScrollLayout mScrollListener = listener; } - /** - * Set rounded rect clipping bounds on this view. - */ + /** Sets rounded rect where the view is allowed to draw. */ @Override - public void setScrimClippingShape(@Nullable ShadeScrimShape shape) { + public void setClippingShape(@Nullable ShadeScrimShape shape) { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; - if (Objects.equals(mScrollViewFields.getScrimClippingShape(), shape)) return; - mScrollViewFields.setScrimClippingShape(shape); + if (Objects.equals(mScrollViewFields.getClippingShape(), shape)) return; + mScrollViewFields.setClippingShape(shape); mShouldUseRoundedRectClipping = shape != null; mRoundedClipPath.reset(); if (shape != null) { @@ -5933,17 +5936,36 @@ public class NotificationStackScrollLayout mRoundedRectClippingTop = (int) bounds.getTop(); mRoundedRectClippingRight = (int) bounds.getRight(); mRoundedRectClippingBottom = (int) bounds.getBottom(); - mBgCornerRadii[0] = shape.getTopRadius(); - mBgCornerRadii[1] = shape.getTopRadius(); - mBgCornerRadii[2] = shape.getTopRadius(); - mBgCornerRadii[3] = shape.getTopRadius(); - mBgCornerRadii[4] = shape.getBottomRadius(); - mBgCornerRadii[5] = shape.getBottomRadius(); - mBgCornerRadii[6] = shape.getBottomRadius(); - mBgCornerRadii[7] = shape.getBottomRadius(); + mRoundedClipCornerRadii[0] = shape.getTopRadius(); + mRoundedClipCornerRadii[1] = shape.getTopRadius(); + mRoundedClipCornerRadii[2] = shape.getTopRadius(); + mRoundedClipCornerRadii[3] = shape.getTopRadius(); + mRoundedClipCornerRadii[4] = shape.getBottomRadius(); + mRoundedClipCornerRadii[5] = shape.getBottomRadius(); + mRoundedClipCornerRadii[6] = shape.getBottomRadius(); + mRoundedClipCornerRadii[7] = shape.getBottomRadius(); mRoundedClipPath.addRoundRect( bounds.getLeft(), bounds.getTop(), bounds.getRight(), bounds.getBottom(), - mBgCornerRadii, Path.Direction.CW); + mRoundedClipCornerRadii, Path.Direction.CW); + } + invalidate(); + } + + @Override + public void setNegativeClippingShape(@Nullable ShadeScrimShape shape) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + if (Objects.equals(mScrollViewFields.getNegativeClippingShape(), shape)) return; + + mScrollViewFields.setNegativeClippingShape(shape); + mShouldUseNegativeRoundedRectClipping = shape != null; + mNegativeRoundedClipPath.reset(); + if (shape != null) { + ShadeScrimBounds bounds = shape.getBounds(); + float bottomRadius = shape.getBottomRadius(); + mNegativeRoundedClipPath.addRoundRect( + bounds.getLeft(), bounds.getTop(), bounds.getRight(), bounds.getBottom(), + new float[]{0, 0, 0, 0, bottomRadius, bottomRadius, bottomRadius, bottomRadius}, + Path.Direction.CW); } invalidate(); } @@ -5956,21 +5978,22 @@ public class NotificationStackScrollLayout SceneContainerFlag.assertInLegacyMode(); if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right && mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top - && mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) { + && mRoundedClipCornerRadii[0] == topRadius + && mRoundedClipCornerRadii[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; + mRoundedClipCornerRadii[0] = topRadius; + mRoundedClipCornerRadii[1] = topRadius; + mRoundedClipCornerRadii[2] = topRadius; + mRoundedClipCornerRadii[3] = topRadius; + mRoundedClipCornerRadii[4] = bottomRadius; + mRoundedClipCornerRadii[5] = bottomRadius; + mRoundedClipCornerRadii[6] = bottomRadius; + mRoundedClipCornerRadii[7] = bottomRadius; updateRoundedClipPath(); } @@ -5992,7 +6015,7 @@ public class NotificationStackScrollLayout mRoundedRectClippingTop + mRoundedRectClippingYTranslation, mRoundedRectClippingRight, mRoundedRectClippingBottom + mRoundedRectClippingYTranslation, - mBgCornerRadii, Path.Direction.CW); + mRoundedClipCornerRadii, Path.Direction.CW); if (mShouldUseRoundedRectClipping) { invalidate(); } @@ -6116,35 +6139,49 @@ public class NotificationStackScrollLayout } @Override - protected void dispatchDraw(Canvas canvas) { - if (mShouldUseRoundedRectClipping && !mLaunchingNotification) { + protected void dispatchDraw(@NonNull Canvas canvas) { + if (!mLaunchingNotification) { // When launching notifications, we're clipping the children individually instead of in // dispatchDraw - // Let's clip rounded. - canvas.clipPath(mRoundedClipPath); + if (mShouldUseRoundedRectClipping) { + // Let's clip rounded. + canvas.clipPath(mRoundedClipPath); + } + if (mShouldUseNegativeRoundedRectClipping) { + // subtract the negative path if it is defined + canvas.clipOutPath(mNegativeRoundedClipPath); + } } super.dispatchDraw(canvas); } @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - if (mShouldUseRoundedRectClipping && mLaunchingNotification) { + boolean shouldUseClipping = + mShouldUseRoundedRectClipping || mShouldUseNegativeRoundedRectClipping; + if (mLaunchingNotification && shouldUseClipping) { // Let's clip children individually during notification launch canvas.save(); ExpandableView expandableView = (ExpandableView) child; Path clipPath; + Path clipOutPath; if (expandableView.isExpandAnimationRunning() || ((ExpandableView) child).hasExpandingChild()) { // When launching the notification, it is not clipped by this layout, but by the // view itself. This is because the view is Translating in Z, where this clipPath // wouldn't apply. clipPath = null; + clipOutPath = null; } else { clipPath = mRoundedClipPath; + clipOutPath = mNegativeRoundedClipPath; } - if (clipPath != null) { + if (mShouldUseRoundedRectClipping && clipPath != null) { canvas.clipPath(clipPath); } + if (mShouldUseNegativeRoundedRectClipping && clipOutPath != null) { + canvas.clipOutPath(clipOutPath); + } boolean result = super.drawChild(canvas, child, drawingTime); canvas.restore(); return result; 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 c717e3b229be..dc0fae80d041 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 @@ -1207,7 +1207,11 @@ public class NotificationStackScrollLayoutController implements Dumpable { return mView.getEmptyShadeViewHeight(); } - /** Set the max alpha for keyguard */ + /** + * Controls fading out Notifications during animations over the LockScreen, such opening or + * closing the shade. Note that we don't restrict Notification alpha in certain cases, + * like when the Shade is opened from a HUN. + */ public void setMaxAlphaForKeyguard(float alpha, String source) { mMaxAlphaForKeyguard = alpha; mMaxAlphaForKeyguardSource = source; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt index fa20e43d0534..2593ef07751b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt @@ -33,7 +33,10 @@ import java.util.function.Consumer */ class ScrollViewFields { /** Used to produce the clipping path */ - var scrimClippingShape: ShadeScrimShape? = null + var clippingShape: ShadeScrimShape? = null + + /** Used to produce a negative clipping path */ + var negativeClippingShape: ShadeScrimShape? = null /** Scroll state of the notification shade. */ var scrollState: ShadeScrollState = ShadeScrollState() @@ -97,7 +100,8 @@ class ScrollViewFields { fun dump(pw: IndentingPrintWriter) { pw.printSection("StackViewStates") { - pw.println("scrimClippingShape", scrimClippingShape) + pw.println("scrimClippingShape", clippingShape) + pw.println("negativeClippingShape", negativeClippingShape) pw.println("scrollState", scrollState) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt index 5ec4c8988697..1d196c2fc079 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.stack.data.repository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState import java.util.function.Consumer import javax.inject.Inject @@ -42,7 +43,15 @@ class NotificationPlaceholderRepository @Inject constructor() { * * When `null`, clipping should not be applied to notifications. */ - val shadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null) + val notificationShadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null) + + /** + * The shape of the QuickSettings overlay panel. Used to clip Notification content when the QS + * covers it. + * + * When `null`, it doesn't affect notification clipping. + */ + val qsPanelShape = MutableStateFlow<ShadeScrimShape?>(null) /** height made available to the notifications in the size-constrained mode of lock screen. */ val constrainedAvailableSpace = MutableStateFlow(0) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index d4dd1d4b9e0b..406a0dbb120f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -26,6 +26,7 @@ import com.android.systemui.statusbar.notification.stack.data.repository.Notific import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding +import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState import java.util.function.Consumer import javax.inject.Inject @@ -47,8 +48,11 @@ constructor( shadeInteractor: ShadeInteractor, ) { /** The bounds of the notification stack in the current scene. */ - val shadeScrimBounds: StateFlow<ShadeScrimBounds?> = - placeholderRepository.shadeScrimBounds.asStateFlow() + val notificationShadeScrimBounds: StateFlow<ShadeScrimBounds?> = + placeholderRepository.notificationShadeScrimBounds.asStateFlow() + + /** The shape of the QuickSettingsShadeOverlay panel */ + val qsPanelShape: StateFlow<ShadeScrimShape?> = placeholderRepository.qsPanelShape.asStateFlow() /** * Whether the stack is expanding from GONE-with-HUN to SHADE @@ -119,9 +123,15 @@ constructor( } /** Sets the position of the notification stack in the current scene. */ - fun setShadeScrimBounds(bounds: ShadeScrimBounds?) { - check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" } - placeholderRepository.shadeScrimBounds.value = bounds + fun setNotificationShadeScrimBounds(bounds: ShadeScrimBounds?) { + checkValidBounds(bounds) + placeholderRepository.notificationShadeScrimBounds.value = bounds + } + + /** Sets the bounds of the QuickSettings overlay panel */ + fun setQsPanelShape(shape: ShadeScrimShape?) { + checkValidBounds(shape?.bounds) + placeholderRepository.qsPanelShape.value = shape } /** Updates the current scroll state of the notification shade. */ @@ -156,4 +166,8 @@ constructor( fun setConstrainedAvailableSpace(height: Int) { placeholderRepository.constrainedAvailableSpace.value = height } + + private fun checkValidBounds(bounds: ShadeScrimBounds?) { + check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index d302fb67dddb..5fec0965f6a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -49,8 +49,14 @@ interface NotificationScrollView { /** Max alpha for this view */ fun setMaxAlpha(alpha: Float) - /** Set the clipping bounds used when drawing */ - fun setScrimClippingShape(shape: ShadeScrimShape?) + /** Sets a clipping shape, which defines the drawable area of this view. */ + fun setClippingShape(shape: ShadeScrimShape?) + + /** + * Sets a clipping shape, which defines the non-drawable area of this view. The final drawing + * area is the difference of the clipping shape, and the negative clipping shape. + */ + fun setNegativeClippingShape(shape: ShadeScrimShape?) /** set the y position in px of the top of the stack in this view's coordinates */ fun setStackTop(stackTop: Float) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index 1d7e658932ac..6385d53dbc8b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -99,6 +99,9 @@ constructor( .inflate(R.layout.status_bar_notification_shelf, view, false) as NotificationShelf view.setShelf(shelf) + // Create viewModels once, and only when needed. + val footerViewModel by lazy { viewModel.footerViewModelFactory.create() } + val emptyShadeViewModel by lazy { viewModel.emptyShadeViewModelFactory.create() } view.repeatWhenAttached { lifecycleScope.launch { if (SceneContainerFlag.isEnabled) { @@ -109,12 +112,18 @@ constructor( val hasNonClearableSilentNotifications: StateFlow<Boolean> = viewModel.hasNonClearableSilentNotifications.stateIn(this) - launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) } + launch { + reinflateAndBindFooter( + footerViewModel, + view, + hasNonClearableSilentNotifications, + ) + } launch { if (ModesEmptyShadeFix.isEnabled) { - reinflateAndBindEmptyShade(view) + reinflateAndBindEmptyShade(emptyShadeViewModel, view) } else { - bindEmptyShadeLegacy(viewModel.emptyShadeViewFactory.create(), view) + bindEmptyShadeLegacy(emptyShadeViewModel, view) } } launch { bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications) } @@ -134,31 +143,30 @@ constructor( } private suspend fun reinflateAndBindFooter( + footerViewModel: FooterViewModel, parentView: NotificationStackScrollLayout, hasNonClearableSilentNotifications: StateFlow<Boolean>, ) { - viewModel.footer.getOrNull()?.let { footerViewModel -> - // The footer needs to be re-inflated every time the theme or the font size changes. - configuration - .inflateLayout<FooterView>( - if (NotifRedesignFooter.isEnabled) R.layout.notification_2025_footer - else R.layout.status_bar_notification_footer, - parentView, - attachToRoot = false, - ) - .flowOn(backgroundDispatcher) - .collectLatest { footerView: FooterView -> - traceAsync("bind FooterView") { - parentView.setFooterView(footerView) - bindFooter( - footerView, - footerViewModel, - parentView, - hasNonClearableSilentNotifications, - ) - } + // The footer needs to be re-inflated every time the theme or the font size changes. + configuration + .inflateLayout<FooterView>( + if (NotifRedesignFooter.isEnabled) R.layout.notification_2025_footer + else R.layout.status_bar_notification_footer, + parentView, + attachToRoot = false, + ) + .flowOn(backgroundDispatcher) + .collectLatest { footerView: FooterView -> + traceAsync("bind FooterView") { + parentView.setFooterView(footerView) + bindFooter( + footerView, + footerViewModel, + parentView, + hasNonClearableSilentNotifications, + ) } - } + } } /** @@ -219,7 +227,10 @@ constructor( notificationActivityStarter.get().startHistoryIntent(view, /* showHistory= */ true) } - private suspend fun reinflateAndBindEmptyShade(parentView: NotificationStackScrollLayout) { + private suspend fun reinflateAndBindEmptyShade( + emptyShadeViewModel: EmptyShadeViewModel, + parentView: NotificationStackScrollLayout, + ) { ModesEmptyShadeFix.assertInNewMode() // The empty shade needs to be re-inflated every time the theme or the font size // changes. @@ -233,7 +244,7 @@ constructor( .collectLatest { emptyShadeView: EmptyShadeView -> traceAsync("bind EmptyShadeView") { parentView.setEmptyShadeView(emptyShadeView) - bindEmptyShade(emptyShadeView, viewModel.emptyShadeViewFactory.create()) + bindEmptyShade(emptyShadeView, emptyShadeViewModel) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index ef68b4de5291..8709d27bddcc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -79,8 +79,17 @@ constructor( launch { viewModel - .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset) - .collectTraced { view.setScrimClippingShape(it) } + .notificationScrimShape( + cornerRadius = scrimRadius, + viewLeftOffset = viewLeftOffset, + ) + .collectTraced { view.setClippingShape(it) } + } + + launch { + viewModel.qsScrimShape(viewLeftOffset = viewLeftOffset).collectTraced { + view.setNegativeClippingShape(it) + } } launch { viewModel.maxAlpha.collectTraced { view.setMaxAlpha(it) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt index fcc671a5bae6..5ed1889de01e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt @@ -56,8 +56,8 @@ class NotificationListViewModel constructor( val shelf: NotificationShelfViewModel, val hideListViewModel: HideListViewModel, - val footer: Optional<FooterViewModel>, - val emptyShadeViewFactory: EmptyShadeViewModel.Factory, + val footerViewModelFactory: FooterViewModel.Factory, + val emptyShadeViewModelFactory: EmptyShadeViewModel.Factory, val logger: Optional<NotificationLoggerViewModel>, activeNotificationsInteractor: ActiveNotificationsInteractor, notificationStackInteractor: NotificationStackInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index 1bb205cbcb61..80ebf43baf92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -59,7 +59,7 @@ class NotificationScrollViewModel @AssistedInject constructor( dumpManager: DumpManager, - stackAppearanceInteractor: NotificationStackAppearanceInteractor, + private val stackAppearanceInteractor: NotificationStackAppearanceInteractor, shadeInteractor: ShadeInteractor, private val remoteInputInteractor: RemoteInputInteractor, private val sceneInteractor: SceneInteractor, @@ -221,7 +221,7 @@ constructor( private val shadeScrimClipping: Flow<ShadeScrimClipping?> = combine( qsAllowsClipping, - stackAppearanceInteractor.shadeScrimBounds, + stackAppearanceInteractor.notificationShadeScrimBounds, stackAppearanceInteractor.shadeScrimRounding, ) { qsAllowsClipping, bounds, rounding -> bounds?.takeIf { qsAllowsClipping }?.let { ShadeScrimClipping(it, rounding) } @@ -229,7 +229,7 @@ constructor( .distinctUntilChanged() .dumpWhileCollecting("stackClipping") - fun shadeScrimShape( + fun notificationScrimShape( cornerRadius: Flow<Int>, viewLeftOffset: Flow<Int>, ): Flow<ShadeScrimShape?> = @@ -243,6 +243,12 @@ constructor( } .dumpWhileCollecting("shadeScrimShape") + fun qsScrimShape(viewLeftOffset: Flow<Int>): Flow<ShadeScrimShape?> = + combine(stackAppearanceInteractor.qsPanelShape, viewLeftOffset) { shape, leftOffset -> + shape?.let { it.copy(bounds = it.bounds.minus(leftOffset = leftOffset)) } + } + .dumpWhileCollecting("qsScrimShape") + /** * Max alpha to apply directly to the view based on the compose placeholder. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 49cd7cb4fb8d..5d550226a79e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlagsClassic @@ -40,7 +41,6 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -import com.android.app.tracing.coroutines.launchTraced as launch /** * ViewModel used by the Notification placeholders inside the scene container to update the @@ -89,7 +89,7 @@ constructor( /** Notifies that the bounds of the notification scrim have changed. */ fun onScrimBoundsChanged(bounds: ShadeScrimBounds?) { - interactor.setShadeScrimBounds(bounds) + interactor.setNotificationShadeScrimBounds(bounds) } /** Sets the available space */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index aa1308931f99..3f44f7bdef90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -234,7 +234,7 @@ private constructor( ) } if (ShadeWindowGoesAround.isEnabled && event.action == MotionEvent.ACTION_DOWN) { - lazyStatusBarShadeDisplayPolicy.get().onStatusBarTouched(context.displayId) + lazyStatusBarShadeDisplayPolicy.get().onStatusBarTouched(event, mView.width) } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt index 284e23e5a288..47c82e309d9b 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt @@ -26,7 +26,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInteropFilter @@ -48,8 +51,13 @@ fun GestureTutorialScreen( onBack: () -> Unit, ) { BackHandler(onBack = onBack) + var cachedTutorialState: TutorialActionState by + rememberSaveable(stateSaver = TutorialActionState.stateSaver()) { + mutableStateOf(NotStarted) + } val easterEggTriggered by easterEggTriggeredFlow.collectAsStateWithLifecycle(false) - val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(NotStarted) + val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(cachedTutorialState) + cachedTutorialState = tutorialState TouchpadGesturesHandlingBox( motionEventConsumer, tutorialState, diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt index 71fe22ba4b01..9cf02f26c9f7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt @@ -82,10 +82,6 @@ constructor( ringerMode: RingerMode?, ): Int { val isStreamOffline = level == 0 || isMuted - when (ringerMode?.value) { - AudioManager.RINGER_MODE_VIBRATE -> return R.drawable.ic_volume_ringer_vibrate - AudioManager.RINGER_MODE_SILENT -> return R.drawable.ic_ring_volume_off - } if (isRoutedToBluetooth) { return if (stream == AudioManager.STREAM_VOICE_CALL) { R.drawable.ic_volume_bt_sco @@ -98,29 +94,39 @@ constructor( } } + val isLevelLow = level < (levelMax + levelMin) / 2 return if (isStreamOffline) { + val ringerOfflineIcon = + when (ringerMode?.value) { + AudioManager.RINGER_MODE_VIBRATE -> return R.drawable.ic_volume_ringer_vibrate + AudioManager.RINGER_MODE_SILENT -> return R.drawable.ic_ring_volume_off + else -> null + } when (stream) { AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media_mute - AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer_mute + AudioManager.STREAM_NOTIFICATION -> + ringerOfflineIcon ?: R.drawable.ic_volume_ringer_mute + AudioManager.STREAM_RING -> ringerOfflineIcon ?: R.drawable.ic_volume_ringer_vibrate AudioManager.STREAM_ALARM -> R.drawable.ic_volume_alarm_mute AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system_mute else -> null - } ?: getIconForStream(stream) - } else { - if (level < (levelMax + levelMin) / 2) { - // This icon is different on TV - R.drawable.ic_volume_media_low - } else { - getIconForStream(stream) } - } + } else { + null + } ?: getIconForStream(stream = stream, isLevelLow = isLevelLow) } @DrawableRes - private fun getIconForStream(stream: Int): Int { + private fun getIconForStream(stream: Int, isLevelLow: Boolean): Int { return when (stream) { AudioManager.STREAM_ACCESSIBILITY -> R.drawable.ic_volume_accessibility - AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media + AudioManager.STREAM_MUSIC -> + if (isLevelLow) { + // This icon is different on TV + R.drawable.ic_volume_media_low + } else { + R.drawable.ic_volume_media + } AudioManager.STREAM_RING -> R.drawable.ic_ring_volume AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer AudioManager.STREAM_ALARM -> R.drawable.ic_alarm @@ -135,7 +141,9 @@ constructor( * affect the [stream] */ private fun ringerModeForStream(stream: Int): Flow<RingerMode?> { - return if (stream == AudioManager.STREAM_RING) { + return if ( + stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION + ) { audioVolumeInteractor.ringerMode } else { flowOf(null) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt deleted file mode 100644 index 134c40da1033..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.android.systemui.bouncer.data.repository - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.testScope -import com.android.systemui.log.table.TableLogBuffer -import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.time.SystemClock -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(AndroidJUnit4::class) -class KeyguardBouncerRepositoryTest : SysuiTestCase() { - - @Mock private lateinit var systemClock: SystemClock - @Mock private lateinit var bouncerLogger: TableLogBuffer - - private val kosmos = testKosmos() - private val testScope = kosmos.testScope - - lateinit var underTest: KeyguardBouncerRepository - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - underTest = - KeyguardBouncerRepositoryImpl( - systemClock, - testScope.backgroundScope, - bouncerLogger, - ) - } - - @Test - fun changingFlowValueTriggersLogging() = - testScope.runTest { - underTest.setPrimaryShow(true) - Mockito.verify(bouncerLogger) - .logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any()) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 3763282cdebc..6ac20d40f2dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -881,7 +881,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @EnableSceneContainer public void testIsInsideScrollableRegion_noOffset() { mStackScroller.setLeftTopRightBottom(0, 0, 1000, 2000); - mStackScroller.setScrimClippingShape(createScrimShape(100, 500, 900, 2000)); + mStackScroller.setClippingShape(createScrimShape(100, 500, 900, 2000)); MotionEvent event1 = transformEventForView(createMotionEvent(500f, 400f), mStackScroller); assertThat(mStackScroller.isInScrollableRegion(event1)).isFalse(); @@ -900,7 +900,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @EnableSceneContainer public void testIsInsideScrollableRegion_offset() { mStackScroller.setLeftTopRightBottom(1000, 0, 2000, 2000); - mStackScroller.setScrimClippingShape(createScrimShape(100, 500, 900, 2000)); + mStackScroller.setClippingShape(createScrimShape(100, 500, 900, 2000)); MotionEvent event1 = transformEventForView(createMotionEvent(1500f, 400f), mStackScroller); assertThat(mStackScroller.isInScrollableRegion(event1)).isFalse(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index d3135026ce06..437ccb6a9821 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -63,7 +63,6 @@ import com.android.systemui.unfold.SysUIUnfoldComponent import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel -import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.whenever import com.android.systemui.util.view.ViewUtil @@ -75,7 +74,6 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.never @@ -83,6 +81,8 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.eq @SmallTest @RunWith(AndroidJUnit4::class) @@ -438,25 +438,28 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Test @EnableFlags(ShadeWindowGoesAround.FLAG_NAME) fun onTouch_actionDown_propagatesToDisplayPolicy() { - controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + controller.onTouch(event) - verify(statusBarTouchShadeDisplayPolicy).onStatusBarTouched(eq(mContext.displayId)) + verify(statusBarTouchShadeDisplayPolicy).onStatusBarTouched(eq(event), any()) } @Test @EnableFlags(ShadeWindowGoesAround.FLAG_NAME) fun onTouch_actionUp_notPropagatesToDisplayPolicy() { - controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) + controller.onTouch(event) - verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any()) + verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any(), any()) } @Test @DisableFlags(ShadeWindowGoesAround.FLAG_NAME) fun onTouch_shadeWindowGoesAroundDisabled_notPropagatesToDisplayPolicy() { - controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + controller.onTouch(event) - verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any()) + verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(eq(event), any()) } @Test diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt index 703e2d1db200..f95f95721ce2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt @@ -23,9 +23,18 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow() private val _primaryBouncerStartingToHide = MutableStateFlow(false) override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow() - private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null) override val primaryBouncerStartingDisappearAnimation = - _primaryBouncerDisappearAnimation.asStateFlow() + MutableSharedFlow<Runnable?>(extraBufferCapacity = 2, replay = 1) + + override fun isPrimaryBouncerStartingDisappearAnimation(): Boolean { + val replayCache = primaryBouncerStartingDisappearAnimation.replayCache + return if (!replayCache.isEmpty()) { + replayCache.last() != null + } else { + false + } + } + private val _primaryBouncerScrimmed = MutableStateFlow(false) override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow() private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncerConstants.EXPANSION_HIDDEN) @@ -53,6 +62,8 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos MutableStateFlow(KeyguardSecurityModel.SecurityMode.Invalid) override var bouncerDismissActionModel: BouncerDismissActionModel? = null + override fun isDebuggable() = true + override fun setPrimaryScrimmed(isScrimmed: Boolean) { _primaryBouncerScrimmed.value = isScrimmed } @@ -74,7 +85,7 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos } override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) { - _primaryBouncerDisappearAnimation.value = runnable + primaryBouncerStartingDisappearAnimation.tryEmit(runnable) } override fun setPanelExpansion(panelExpansion: Float) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt index 487049740079..e30e92020706 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt @@ -33,10 +33,7 @@ import kotlinx.coroutines.flow.asStateFlow @SysUISingleton class FakeConfigurationRepository @Inject constructor() : ConfigurationRepository { private val _onAnyConfigurationChange = - MutableSharedFlow<Unit>( - replay = 1, - onBufferOverflow = BufferOverflow.DROP_OLDEST, - ) + MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) override val onAnyConfigurationChange: Flow<Unit> = _onAnyConfigurationChange.asSharedFlow() private val _onConfigurationChange = @@ -53,7 +50,7 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor get() = _onMovedToDisplay private val _scaleForResolution = MutableStateFlow(1f) - override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow() + override val scaleForResolution: StateFlow<Float> = _scaleForResolution.asStateFlow() private val pixelSizes = mutableMapOf<Int, MutableStateFlow<Int>>() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt index fb6699c44d62..91f1e1c3e0c8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt @@ -17,18 +17,16 @@ package com.android.systemui.compose import androidx.compose.runtime.snapshots.Snapshot -import com.android.systemui.kosmos.runCurrent import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest /** * Runs the given test [block] in a [TestScope] that's set up such that the Compose snapshot state - * is settled eagerly. This is the Compose equivalent to using an [UnconfinedTestDispatcher] or - * using [runCurrent] a lot. + * writes are properly applied to the global snapshot. This is for instance necessary if your test + * is using `snapshotFlow {}` or any other mechanism that is observing the global snapshot. * - * Note that this shouldn't be needed or used in a Compose test environment. + * Note that this isn't needed in a Compose test environment, e.g. if you use the + * `Compose(Content)TestRule`. */ fun TestScope.runTestWithSnapshots(block: suspend TestScope.() -> Unit) { val handle = Snapshot.registerGlobalWriteObserver { Snapshot.sendApplyNotifications() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt index 046284861e2b..4f3b8f3541e1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt @@ -20,12 +20,14 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory +import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor val Kosmos.quickSettingsShadeOverlayContentViewModel: QuickSettingsShadeOverlayContentViewModel by Kosmos.Fixture { QuickSettingsShadeOverlayContentViewModel( shadeInteractor = shadeInteractor, sceneInteractor = sceneInteractor, + notificationStackAppearanceInteractor = notificationStackAppearanceInteractor, shadeHeaderViewModelFactory = shadeHeaderViewModelFactory, quickSettingsContainerViewModelFactory = quickSettingsContainerViewModelFactory, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt index 636cb37adf03..aaef27d257c5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt @@ -23,7 +23,11 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.shade.display.AnyExternalShadeDisplayPolicy import com.android.systemui.shade.display.DefaultDisplayShadePolicy import com.android.systemui.shade.display.ShadeDisplayPolicy +import com.android.systemui.shade.display.ShadeExpansionIntent import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy +import com.android.systemui.shade.domain.interactor.notificationElement +import com.android.systemui.shade.domain.interactor.qsElement +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.util.settings.fakeGlobalSettings val Kosmos.defaultShadeDisplayPolicy: DefaultDisplayShadePolicy by @@ -37,16 +41,20 @@ val Kosmos.anyExternalShadeDisplayPolicy: AnyExternalShadeDisplayPolicy by ) } -val Kosmos.focusBasedShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by +val Kosmos.statusBarTouchShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by Kosmos.Fixture { StatusBarTouchShadeDisplayPolicy( displayRepository = displayRepository, backgroundScope = testScope.backgroundScope, keyguardRepository = keyguardRepository, shadeOnDefaultDisplayWhenLocked = false, + shadeInteractor = { shadeInteractor }, + notificationElement = { notificationElement }, + qsShadeElement = { qsElement }, ) } - +val Kosmos.shadeExpansionIntent: ShadeExpansionIntent by + Kosmos.Fixture { statusBarTouchShadeDisplayPolicy } val Kosmos.shadeDisplaysRepository: MutableShadeDisplaysRepository by Kosmos.Fixture { ShadeDisplaysRepositoryImpl( @@ -62,7 +70,7 @@ val Kosmos.shadeDisplayPolicies: Set<ShadeDisplayPolicy> by setOf( defaultShadeDisplayPolicy, anyExternalShadeDisplayPolicy, - focusBasedShadeDisplayPolicy, + statusBarTouchShadeDisplayPolicy, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt index 6e44df833582..923de2dcbf68 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt @@ -23,6 +23,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.shade.ShadeDisplayChangeLatencyTracker import com.android.systemui.shade.ShadeWindowLayoutParams import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository +import com.android.systemui.shade.data.repository.shadeExpansionIntent import java.util.Optional import org.mockito.kotlin.any import org.mockito.kotlin.mock @@ -49,5 +50,6 @@ val Kosmos.shadeDisplaysInteractor by testScope.backgroundScope.coroutineContext, mockedShadeDisplayChangeLatencyTracker, Optional.of(shadeExpandedStateInteractor), + shadeExpansionIntent, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt index 1dc7229a6506..32a30502a370 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt @@ -31,7 +31,6 @@ import com.android.systemui.statusbar.phone.dozeParameters import com.android.systemui.statusbar.policy.data.repository.userSetupRepository import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor import com.android.systemui.user.domain.interactor.userSwitcherInteractor -import org.mockito.kotlin.mock var Kosmos.baseShadeInteractor: BaseShadeInteractor by Kosmos.Fixture { @@ -73,7 +72,19 @@ val Kosmos.shadeInteractorImpl by shadeModeInteractor = shadeModeInteractor, ) } -var Kosmos.mockShadeInteractor: ShadeInteractor by Kosmos.Fixture { mock() } +var Kosmos.notificationElement: NotificationShadeElement by + Kosmos.Fixture { + NotificationShadeElement(shadeInteractor, testScope.backgroundScope.coroutineContext) + } +var Kosmos.qsElement: QSShadeElement by + Kosmos.Fixture { QSShadeElement(shadeInteractor, testScope.backgroundScope.coroutineContext) } val Kosmos.shadeExpandedStateInteractor by - Kosmos.Fixture { ShadeExpandedStateInteractorImpl(shadeInteractor, testScope.backgroundScope) } + Kosmos.Fixture { + ShadeExpandedStateInteractorImpl( + shadeInteractor, + testScope.backgroundScope, + notificationElement, + qsElement, + ) + } val Kosmos.fakeShadeExpandedStateInteractor by Kosmos.Fixture { FakeShadeExpandedStateInteractor() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt index 01cac4c1e030..99323dbd7cce 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt @@ -31,3 +31,8 @@ val Kosmos.footerViewModel by Fixture { shadeInteractor = shadeInteractor, ) } +val Kosmos.footerViewModelFactory: FooterViewModel.Factory by Fixture { + object : FooterViewModel.Factory { + override fun create() = footerViewModel + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt index c3bc744e09b0..fbc2a21b0888 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt @@ -24,7 +24,7 @@ import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.emptyShadeViewModelFactory -import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModel +import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModelFactory import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.notificationShelfViewModel import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackInteractor @@ -35,7 +35,7 @@ val Kosmos.notificationListViewModel by Fixture { NotificationListViewModel( notificationShelfViewModel, hideListViewModel, - Optional.of(footerViewModel), + footerViewModelFactory, emptyShadeViewModelFactory, Optional.of(notificationListLoggerViewModel), activeNotificationsInteractor, diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java index 6467af4355f6..c8c645f1276d 100644 --- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java +++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java @@ -138,6 +138,8 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { @NonNull private final Object mCancellationToken = new Object(); @NonNull private final PacketLossCalculator mPacketLossCalculator; + @Nullable private BroadcastReceiver mDeviceIdleReceiver; + @Nullable private IpSecTransformWrapper mInboundTransform; @Nullable private IpSecTransformState mLastIpSecTransformState; @@ -168,19 +170,21 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { // Register for system broadcasts to monitor idle mode change final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); + + mDeviceIdleReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals( + intent.getAction()) + && mPowerManager.isDeviceIdleMode()) { + mLastIpSecTransformState = null; + } + } + }; getVcnContext() .getContext() .registerReceiver( - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals( - intent.getAction()) - && mPowerManager.isDeviceIdleMode()) { - mLastIpSecTransformState = null; - } - } - }, + mDeviceIdleReceiver, intentFilter, null /* broadcastPermission not required */, mHandler); @@ -338,7 +342,12 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { super.close(); if (mInboundTransform != null) { - mInboundTransform.close(); + mInboundTransform = null; + } + + if (mDeviceIdleReceiver != null) { + getVcnContext().getContext().unregisterReceiver(mDeviceIdleReceiver); + mDeviceIdleReceiver = null; } } diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java index 14853440a129..55829a5fe978 100644 --- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java +++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java @@ -165,7 +165,7 @@ public abstract class NetworkMetricMonitor implements AutoCloseable { } } - /** Set the IpSecTransform that applied to the Network being monitored */ + /** Set the IpSecTransform that is applied to the Network being monitored */ public void setInboundTransform(@NonNull IpSecTransform inTransform) { setInboundTransformInternal(new IpSecTransformWrapper(inTransform)); } diff --git a/services/core/Android.bp b/services/core/Android.bp index d6bffcb7d21d..42385fc5bdb0 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -37,6 +37,7 @@ filegroup { ":framework_native_aidl", ":gsiservice_aidl", ":installd_aidl", + ":mmd_aidl", ":storaged_aidl", ":vold_aidl", ], @@ -246,6 +247,7 @@ java_library_static { "aconfig_new_storage_flags_lib", "powerstats_flags_lib", "locksettings_flags_lib", + "mmd_flags_lib", "profiling_flags_lib", "android.adpf.sessionmanager_aidl-java", "uprobestats_flags_java_lib", diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index 6459016eec75..87222a60d82d 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -564,7 +564,8 @@ public class GestureLauncherService extends SystemService { return Settings.Secure.getIntForUser( context.getContentResolver(), Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE, - LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER, + context.getResources().getInteger( + R.integer.config_doubleTapPowerGestureMultiTargetDefaultAction), userId); } diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index 6858e2941ff9..ef769cf6217c 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -9,6 +9,7 @@ per-file DisplayThread.java = michaelwr@google.com, ogunwale@google.com # Zram writeback per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com +per-file ZramMaintenance.java = kawasin@google.com # ServiceWatcher per-file ServiceWatcher.java = sooniln@google.com diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index b7bc4e4827ef..19e7e062758a 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -41,6 +41,7 @@ import static android.os.storage.OnObbStateChangeListener.ERROR_NOT_MOUNTED; import static android.os.storage.OnObbStateChangeListener.ERROR_PERMISSION_DENIED; import static android.os.storage.OnObbStateChangeListener.MOUNTED; import static android.os.storage.OnObbStateChangeListener.UNMOUNTED; +import static android.mmd.flags.Flags.mmdEnabled; import static com.android.internal.util.XmlUtils.readStringAttribute; import static com.android.internal.util.XmlUtils.writeStringAttribute; @@ -945,12 +946,17 @@ class StorageManagerService extends IStorageManager.Stub }); refreshZramSettings(); - // Schedule zram writeback unless zram is disabled by persist.sys.zram_enabled - String zramPropValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY); - if (!zramPropValue.equals("0") - && mContext.getResources().getBoolean( + if (mmdEnabled()) { + // TODO: b/375432472 - Start zram maintenance only when zram is enabled. + ZramMaintenance.startZramMaintenance(mContext); + } else { + // Schedule zram writeback unless zram is disabled by persist.sys.zram_enabled + String zramPropValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY); + if (!zramPropValue.equals("0") + && mContext.getResources().getBoolean( com.android.internal.R.bool.config_zramWriteback)) { - ZramWriteback.scheduleZramWriteback(mContext); + ZramWriteback.scheduleZramWriteback(mContext); + } } configureTranscoding(); @@ -977,7 +983,7 @@ class StorageManagerService extends IStorageManager.Stub // sole writer. SystemProperties.set(ZRAM_ENABLED_PROPERTY, desiredPropertyValue); // Schedule writeback only if zram is being enabled. - if (desiredPropertyValue.equals("1") + if (!mmdEnabled() && desiredPropertyValue.equals("1") && mContext.getResources().getBoolean( com.android.internal.R.bool.config_zramWriteback)) { ZramWriteback.scheduleZramWriteback(mContext); diff --git a/services/core/java/com/android/server/ZramMaintenance.java b/services/core/java/com/android/server/ZramMaintenance.java new file mode 100644 index 000000000000..cdb48122e321 --- /dev/null +++ b/services/core/java/com/android/server/ZramMaintenance.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.os.IBinder; +import android.os.IMmd; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.util.Slog; + +import java.time.Duration; + +/** + * Schedules zram maintenance (e.g. zram writeback, zram recompression). + * + * <p>ZramMaintenance notifies mmd the good timing to execute zram maintenance based on: + * + * <ul> + * <li>Enough interval has passed. + * <li>The system is idle. + * <li>The battery is not low. + * </ul> + */ +public class ZramMaintenance extends JobService { + private static final String TAG = ZramMaintenance.class.getName(); + // Job id must be unique across all clients of the same uid. ZramMaintenance uses the bug number + // as the job id. + private static final int JOB_ID = 375432472; + private static final ComponentName sZramMaintenance = + new ComponentName("android", ZramMaintenance.class.getName()); + + private static final String FIRST_DELAY_SECONDS_PROP = + "mm.zram.maintenance.first_delay_seconds"; + // The default is 1 hour. + private static final long DEFAULT_FIRST_DELAY_SECONDS = 3600; + private static final String PERIODIC_DELAY_SECONDS_PROP = + "mm.zram.maintenance.periodic_delay_seconds"; + // The default is 1 hour. + private static final long DEFAULT_PERIODIC_DELAY_SECONDS = 3600; + private static final String REQUIRE_DEVICE_IDLE_PROP = + "mm.zram.maintenance.require_device_idle"; + private static final boolean DEFAULT_REQUIRE_DEVICE_IDLE = + true; + private static final String REQUIRE_BATTERY_NOT_LOW_PROP = + "mm.zram.maintenance.require_battry_not_low"; + private static final boolean DEFAULT_REQUIRE_BATTERY_NOT_LOW = + true; + + @Override + public boolean onStartJob(JobParameters params) { + IBinder binder = ServiceManager.getService("mmd"); + if (binder != null) { + IMmd mmd = IMmd.Stub.asInterface(binder); + try { + mmd.doZramMaintenance(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to doZramMaintenance", e); + } + } else { + Slog.w(TAG, "binder not found"); + } + Duration delay = Duration.ofSeconds(SystemProperties.getLong(PERIODIC_DELAY_SECONDS_PROP, + DEFAULT_PERIODIC_DELAY_SECONDS)); + scheduleZramMaintenance(this, delay); + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + return false; + } + + /** + * Starts periodical zram maintenance. + */ + public static void startZramMaintenance(Context context) { + Duration delay = Duration.ofSeconds( + SystemProperties.getLong(FIRST_DELAY_SECONDS_PROP, DEFAULT_FIRST_DELAY_SECONDS)); + scheduleZramMaintenance(context, delay); + } + + private static void scheduleZramMaintenance(Context context, Duration delay) { + JobScheduler js = context.getSystemService(JobScheduler.class); + + if (js != null) { + js.schedule(new JobInfo.Builder(JOB_ID, sZramMaintenance) + .setMinimumLatency(delay.toMillis()) + .setRequiresDeviceIdle( + SystemProperties.getBoolean(REQUIRE_DEVICE_IDLE_PROP, + DEFAULT_REQUIRE_DEVICE_IDLE)) + .setRequiresBatteryNotLow( + SystemProperties.getBoolean(REQUIRE_BATTERY_NOT_LOW_PROP, + DEFAULT_REQUIRE_BATTERY_NOT_LOW)) + .build()); + } + } +} diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index c99e8c8ff799..d3a52543f321 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -155,6 +155,7 @@ public class SettingsToPropertiesMapper { "android_core_networking", "android_health_services", "android_sdk", + "android_kernel", "aoc", "app_widgets", "arc_next", diff --git a/services/core/java/com/android/server/crashrecovery/OWNERS b/services/core/java/com/android/server/crashrecovery/OWNERS index daa02111f71f..02df9860030d 100644 --- a/services/core/java/com/android/server/crashrecovery/OWNERS +++ b/services/core/java/com/android/server/crashrecovery/OWNERS @@ -1,3 +1,2 @@ -ancr@google.com harshitmahajan@google.com robertogil@google.com diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index b530da2a5f5e..ca001b9c7e6d 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -4188,6 +4188,9 @@ public final class DisplayManagerService extends SystemService { } } + /** + * This method must not be called with mCallback held or deadlock will ensue. + */ @Override public void binderDied() { synchronized (mCallback) { @@ -4248,17 +4251,8 @@ public final class DisplayManagerService extends SystemService { } } - return transmitDisplayEvent(displayId, event); - } - - /** - * Transmit a single display event. The client is presumed ready. Return true on success - * and false if the client died. - */ - private boolean transmitDisplayEvent(int displayId, @DisplayEvent int event) { - // The client is ready to receive the event. try { - mCallback.onDisplayEvent(displayId, event); + transmitDisplayEvent(displayId, event); return true; } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify process " @@ -4269,6 +4263,18 @@ public final class DisplayManagerService extends SystemService { } /** + * Transmit a single display event. The client is presumed ready. This throws if the + * client has died; callers must catch and handle the exception. The exception cannot be + * handled directly here because {@link #binderDied()} must not be called whilst holding + * the mCallback lock. + */ + private void transmitDisplayEvent(int displayId, @DisplayEvent int event) + throws RemoteException { + // The client is ready to receive the event. + mCallback.onDisplayEvent(displayId, event); + } + + /** * Return true if the client is interested in this event. */ private boolean shouldSendDisplayEvent(@DisplayEvent int event) { @@ -4376,27 +4382,32 @@ public final class DisplayManagerService extends SystemService { // would be unusual to do so. The method returns true on success. // This is only used if {@link deferDisplayEventsWhenFrozen()} is true. public boolean dispatchPending() { - synchronized (mCallback) { - if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) { - return true; - } - if (!isReadyLocked()) { - return false; - } - for (int i = 0; i < mPendingEvents.size(); i++) { - Event displayEvent = mPendingEvents.get(i); - if (DEBUG) { - Slog.d(TAG, "Send pending display event #" + i + " " - + displayEvent.displayId + "/" - + displayEvent.event + " to " + mUid + "/" + mPid); + try { + synchronized (mCallback) { + if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) { + return true; + } + if (!isReadyLocked()) { + return false; } - if (!transmitDisplayEvent(displayEvent.displayId, displayEvent.event)) { - Slog.d(TAG, "Drop pending events for dead process " + mPid); - break; + for (int i = 0; i < mPendingEvents.size(); i++) { + Event displayEvent = mPendingEvents.get(i); + if (DEBUG) { + Slog.d(TAG, "Send pending display event #" + i + " " + + displayEvent.displayId + "/" + + displayEvent.event + " to " + mUid + "/" + mPid); + } + transmitDisplayEvent(displayEvent.displayId, displayEvent.event); } + mPendingEvents.clear(); + return true; } - mPendingEvents.clear(); - return true; + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + + mPid + " that display topology changed, assuming it died.", ex); + binderDied(); + return false; + } } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java index a41194b898ac..1949d103a0d6 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java @@ -499,9 +499,11 @@ import java.util.List; /* package */ static HubMessage createHubMessage(Message message) { boolean isReliable = (message.flags & Message.FLAG_REQUIRES_DELIVERY_STATUS) != 0; - return new HubMessage.Builder(message.type, message.content) + HubMessage outMessage = new HubMessage.Builder(message.type, message.content) .setResponseRequired(isReliable) .build(); + outMessage.setMessageSequenceNumber(message.sequenceNumber); + return outMessage; } /** diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index d6f7d3bdd4a4..23e9ac5008f7 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -545,6 +545,16 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { for (RoutingSessionInfo session : sessions) { if (session == null) continue; session = assignProviderIdForSession(session); + + if (Flags.enableMirroringInMediaRouter2()) { + var systemSessionCallback = + mSystemSessionCallbacks.get(session.getOriginalId()); + if (systemSessionCallback != null) { + systemSessionCallback.onSessionUpdate(session); + continue; + } + } + int sourceIndex = findSessionByIdLocked(session); if (sourceIndex < 0) { mSessionInfos.add(targetIndex++, session); diff --git a/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java b/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java index ba98a0a9fd4e..fd1bea9ae639 100644 --- a/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java +++ b/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java @@ -25,8 +25,9 @@ import android.media.IRemoteDisplayProvider; import android.media.RemoteDisplayState; import android.os.Handler; import android.os.IBinder; -import android.os.RemoteException; import android.os.IBinder.DeathRecipient; +import android.os.Looper; +import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; import android.util.Slog; @@ -35,10 +36,8 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.Objects; -/** - * Maintains a connection to a particular remote display provider service. - */ -final class RemoteDisplayProviderProxy implements ServiceConnection { +/** Maintains a connection to a particular remote display provider service. */ +final class RemoteDisplayProviderProxy { private static final String TAG = "RemoteDisplayProvider"; // max. 23 chars private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -61,12 +60,15 @@ final class RemoteDisplayProviderProxy implements ServiceConnection { private RemoteDisplayState mDisplayState; private boolean mScheduledDisplayStateChangedCallback; - public RemoteDisplayProviderProxy(Context context, ComponentName componentName, - int userId) { + private final ServiceConnection mServiceConnection = + new ServiceConnectionImpl(); + + /* package */ RemoteDisplayProviderProxy( + Context context, ComponentName componentName, int userId, Looper looper) { mContext = context; mComponentName = componentName; mUserId = userId; - mHandler = new Handler(); + mHandler = new Handler(looper); } public void dump(PrintWriter pw, String prefix) { @@ -190,9 +192,12 @@ final class RemoteDisplayProviderProxy implements ServiceConnection { Intent service = new Intent(RemoteDisplayState.SERVICE_INTERFACE); service.setComponent(mComponentName); try { - mBound = mContext.bindServiceAsUser(service, this, - Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, - new UserHandle(mUserId)); + mBound = + mContext.bindServiceAsUser( + service, + mServiceConnection, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, + new UserHandle(mUserId)); if (!mBound && DEBUG) { Slog.d(TAG, this + ": Bind failed"); } @@ -212,12 +217,11 @@ final class RemoteDisplayProviderProxy implements ServiceConnection { mBound = false; disconnect(); - mContext.unbindService(this); + mContext.unbindService(mServiceConnection); } } - @Override - public void onServiceConnected(ComponentName name, IBinder service) { + private void onServiceConnectedOnHandler(IBinder service) { if (DEBUG) { Slog.d(TAG, this + ": Connected"); } @@ -241,8 +245,7 @@ final class RemoteDisplayProviderProxy implements ServiceConnection { } } - @Override - public void onServiceDisconnected(ComponentName name) { + private void onServiceDisconnectedOnHandler() { if (DEBUG) { Slog.d(TAG, this + ": Service disconnected"); } @@ -322,6 +325,20 @@ final class RemoteDisplayProviderProxy implements ServiceConnection { void onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state); } + // All methods in this class are called on the main thread. + private final class ServiceConnectionImpl implements ServiceConnection { + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mHandler.post(() -> onServiceConnectedOnHandler(service)); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mHandler.post(RemoteDisplayProviderProxy.this::onServiceDisconnectedOnHandler); + } + } + private final class Connection implements DeathRecipient { private final IRemoteDisplayProvider mProvider; private final ProviderCallback mCallback; diff --git a/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java index 64c451d03caa..cc03c805fef0 100644 --- a/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java +++ b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java @@ -121,9 +121,11 @@ public final class RemoteDisplayProviderWatcher { int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name); if (sourceIndex < 0) { RemoteDisplayProviderProxy provider = - new RemoteDisplayProviderProxy(mContext, - new ComponentName(serviceInfo.packageName, serviceInfo.name), - mUserId); + new RemoteDisplayProviderProxy( + mContext, + new ComponentName(serviceInfo.packageName, serviceInfo.name), + mUserId, + mHandler.getLooper()); provider.start(); mProviders.add(targetIndex++, provider); mCallback.addProvider(provider); diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java index 8931e3a1426e..011659a616d3 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java @@ -128,8 +128,20 @@ import java.util.stream.Stream; targetProviderProxyId, existingSession.getProviderId())) { // The currently selected route and target route both belong to the same // provider. We tell the provider to handle the transfer. - targetProviderProxyRecord.requestTransfer( - existingSession.getOriginalId(), serviceTargetRoute); + if (serviceTargetRoute == null) { + notifyRequestFailed( + requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE); + } else { + targetProviderProxyRecord.mProxy.transferToRoute( + requestId, + clientUserHandle, + clientPackageName, + existingSession.getOriginalId(), + targetProviderProxyRecord.mNewOriginalIdToSourceOriginalIdMap.get( + routeOriginalId), + transferReason); + } + return; } else { // The target route is handled by a provider other than the target one. We need // to release the existing session. @@ -429,11 +441,6 @@ import java.util.stream.Stream; } } - public void requestTransfer(String sessionId, MediaRoute2Info targetRoute) { - // TODO: Map the target route to the source route original id. - throw new UnsupportedOperationException("TODO Implement"); - } - public void releaseSession(long requestId, String originalSessionId) { mProxy.releaseSession(requestId, originalSessionId); } @@ -491,18 +498,19 @@ import java.util.stream.Stream; () -> { if (mSessionRecord != null) { mSessionRecord.onSessionUpdate(sessionInfo); + } else { + SystemMediaSessionRecord systemMediaSessionRecord = + new SystemMediaSessionRecord(mProviderId, sessionInfo); + RoutingSessionInfo translatedSession; + synchronized (mLock) { + mSessionRecord = systemMediaSessionRecord; + mPackageNameToSessionRecord.put( + mClientPackageName, systemMediaSessionRecord); + mPendingSessionCreations.remove(mRequestId); + translatedSession = systemMediaSessionRecord.mTranslatedSessionInfo; + } + onSessionOverrideUpdated(translatedSession); } - SystemMediaSessionRecord systemMediaSessionRecord = - new SystemMediaSessionRecord(mProviderId, sessionInfo); - RoutingSessionInfo translatedSession; - synchronized (mLock) { - mSessionRecord = systemMediaSessionRecord; - mPackageNameToSessionRecord.put( - mClientPackageName, systemMediaSessionRecord); - mPendingSessionCreations.remove(mRequestId); - translatedSession = systemMediaSessionRecord.mTranslatedSessionInfo; - } - onSessionOverrideUpdated(translatedSession); }); } @@ -546,7 +554,6 @@ import java.util.stream.Stream; * The same as {@link #mSourceSessionInfo}, except ids are {@link #asSystemRouteId system * provider ids}. */ - @GuardedBy("SystemMediaRoute2Provider2.this.mLock") @NonNull private RoutingSessionInfo mTranslatedSessionInfo; @@ -559,10 +566,10 @@ import java.util.stream.Stream; @Override public void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo) { - RoutingSessionInfo translatedSessionInfo = mTranslatedSessionInfo; + RoutingSessionInfo translatedSessionInfo = asSystemProviderSession(sessionInfo); synchronized (mLock) { mSourceSessionInfo = sessionInfo; - mTranslatedSessionInfo = asSystemProviderSession(sessionInfo); + mTranslatedSessionInfo = translatedSessionInfo; } onSessionOverrideUpdated(translatedSessionInfo); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 32d3970ce549..f3eed7d22bec 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -10435,16 +10435,12 @@ public class NotificationManagerService extends SystemService { } private void scheduleListenerHintsChanged(int state) { - if (!Flags.notificationReduceMessagequeueUsage()) { - mHandler.removeMessages(MESSAGE_LISTENER_HINTS_CHANGED); - } + mHandler.removeMessages(MESSAGE_LISTENER_HINTS_CHANGED); mHandler.obtainMessage(MESSAGE_LISTENER_HINTS_CHANGED, state, 0).sendToTarget(); } private void scheduleInterruptionFilterChanged(int listenerInterruptionFilter) { - if (!Flags.notificationReduceMessagequeueUsage()) { - mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED); - } + mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED); mHandler.obtainMessage( MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED, listenerInterruptionFilter, @@ -10524,14 +10520,9 @@ public class NotificationManagerService extends SystemService { } protected void scheduleSendRankingUpdate() { - if (Flags.notificationReduceMessagequeueUsage()) { + if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) { Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE); sendMessage(m); - } else { - if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) { - Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE); - sendMessage(m); - } } } @@ -10540,12 +10531,8 @@ public class NotificationManagerService extends SystemService { if (lifetimeExtensionRefactor()) { sendMessageDelayed(Message.obtain(this, cancelRunnable), delay); } else { - if (Flags.notificationReduceMessagequeueUsage()) { + if (!hasCallbacks(cancelRunnable)) { sendMessage(Message.obtain(this, cancelRunnable)); - } else { - if (!hasCallbacks(cancelRunnable)) { - sendMessage(Message.obtain(this, cancelRunnable)); - } } } } @@ -10580,9 +10567,7 @@ public class NotificationManagerService extends SystemService { } public void requestSort() { - if (!Flags.notificationReduceMessagequeueUsage()) { - removeMessages(MESSAGE_RANKING_SORT); - } + removeMessages(MESSAGE_RANKING_SORT); Message msg = Message.obtain(); msg.what = MESSAGE_RANKING_SORT; sendMessage(msg); diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index c1ca9c23aef5..b4a8aee66c7c 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -51,13 +51,6 @@ flag { } flag { - name: "notification_reduce_messagequeue_usage" - namespace: "systemui" - description: "When this flag is on, NMS will no longer call removeMessage() and hasCallbacks() on Handler" - bug: "311051285" -} - -flag { name: "notification_test" namespace: "systemui" description: "Timing test, no functionality" diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java index 3660607d8764..bf0e77e03171 100644 --- a/services/core/java/com/android/server/pm/BroadcastHelper.java +++ b/services/core/java/com/android/server/pm/BroadcastHelper.java @@ -86,8 +86,6 @@ import java.util.function.Supplier; */ public final class BroadcastHelper { private static final boolean DEBUG_BROADCASTS = false; - private static final String PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED = - "android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"; private final UserManagerInternal mUmInternal; private final ActivityManagerInternal mAmInternal; @@ -398,8 +396,7 @@ public final class BroadcastHelper { sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, notExportedComponentNames, packageUid, reason, userIds, instantUserIds, broadcastAllowList, "android" /* targetPackageName */, - new String[]{ - PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED}); + null /* requiredPermissions */); } // Second, send the PACKAGE_CHANGED broadcast to the application itself. diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index f4d4c5be035e..b85e6894b910 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -33,6 +33,7 @@ import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY; import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN; import static android.os.UserManager.USER_OPERATION_ERROR_USER_RESTRICTED; import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; +import static android.os.UserManager.supportsMultipleUsers; import static android.provider.Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT; import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID; @@ -1156,7 +1157,7 @@ public class UserManagerService extends IUserManager.Stub { showHsumNotificationIfNeeded(); - if (Flags.addUiForSoundsFromBackgroundUsers()) { + if (shouldShowNotificationForBackgroundUserSounds()) { new BackgroundUserSoundNotifier(mContext); } } @@ -8486,6 +8487,17 @@ public class UserManagerService extends IUserManager.Stub { } /** + * @hide + * Checks whether to show a notification for sounds (e.g., alarms, timers, etc.) from + * background users. + */ + public static boolean shouldShowNotificationForBackgroundUserSounds() { + return Flags.addUiForSoundsFromBackgroundUsers() && Resources.getSystem().getBoolean( + com.android.internal.R.bool.config_showNotificationForBackgroundUserAlarms) + && supportsMultipleUsers(); + } + + /** * Returns instance of {@link com.android.server.pm.UserJourneyLogger}. */ public UserJourneyLogger getUserJourneyLogger() { diff --git a/services/core/java/com/android/server/power/hint/TEST_MAPPING b/services/core/java/com/android/server/power/hint/TEST_MAPPING index fd81277e9ba4..545070050977 100644 --- a/services/core/java/com/android/server/power/hint/TEST_MAPPING +++ b/services/core/java/com/android/server/power/hint/TEST_MAPPING @@ -15,22 +15,5 @@ {"exclude-annotation": "org.junit.Ignore"} ] } - ], - "postsubmit": [ - { - "name": "CtsSystemHealthTestCases", - "options": [ - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] - }, - { - "name": "CtsOsTestCases", - "options": [ - {"include-filter": "android.os.health.cts.HeadroomTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] - } ] }
\ No newline at end of file diff --git a/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java index 2452dc59bea5..6798a6146ae0 100644 --- a/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java +++ b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java @@ -17,7 +17,6 @@ package com.android.server.security.authenticationpolicy; import static android.Manifest.permission.MANAGE_SECURE_LOCK_DEVICE; -import static android.security.Flags.disableAdaptiveAuthCounterLock; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; @@ -40,7 +39,6 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.SystemClock; -import android.provider.Settings; import android.security.authenticationpolicy.AuthenticationPolicyManager; import android.security.authenticationpolicy.DisableSecureLockDeviceParams; import android.security.authenticationpolicy.EnableSecureLockDeviceParams; @@ -253,17 +251,6 @@ public class AuthenticationPolicyService extends SystemService { return; } - if (disableAdaptiveAuthCounterLock() && Build.IS_DEBUGGABLE) { - final boolean disabled = Settings.Secure.getIntForUser( - getContext().getContentResolver(), - Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, - 0 /* default */, userId) != 0; - if (disabled) { - Slog.d(TAG, "not locking (disabled by user)"); - return; - } - } - //TODO: additionally consider the trust signal before locking device lockDevice(userId); } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index ae726c15ed79..a5805043ac42 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -79,6 +79,7 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.util.DumpUtils; import com.android.server.SystemService; import com.android.server.pm.BackgroundUserSoundNotifier; +import com.android.server.pm.UserManagerService; import com.android.server.vibrator.VibrationSession.CallerInfo; import com.android.server.vibrator.VibrationSession.DebugInfo; import com.android.server.vibrator.VibrationSession.Status; @@ -200,7 +201,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { VibratorManagerService.this::shouldCancelOnScreenOffLocked, Status.CANCELLED_BY_SCREEN_OFF); } - } else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers() + } else if (UserManagerService.shouldShowNotificationForBackgroundUserSounds() && intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) { synchronized (mLock) { maybeClearCurrentAndNextSessionsLocked( @@ -324,7 +325,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); - if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()) { + if (UserManagerService.shouldShowNotificationForBackgroundUserSounds()) { filter.addAction(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND); } context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index dca65a183f18..a24522a5080d 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -482,6 +482,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final String launchedFromPackage; // always the package who started the activity. @Nullable final String launchedFromFeatureId; // always the feature in launchedFromPackage + @LaunchSourceType int mLaunchSourceType; // latest launch source type final Intent intent; // the original intent that generated us final String shortComponentName; // the short component name of the intent @@ -2330,6 +2331,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mLaunchSourceType = determineLaunchSourceType(launchFromUid, caller); } + @LaunchSourceType private int determineLaunchSourceType(int launchFromUid, WindowProcessController caller) { if (launchFromUid == Process.SYSTEM_UID || launchFromUid == Process.ROOT_UID) { return LAUNCH_SOURCE_TYPE_SYSTEM; @@ -5474,7 +5476,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } boolean canAffectSystemUiFlags() { - return task != null && task.canAffectSystemUiFlags() && isVisible() + final TaskFragment taskFragment = getTaskFragment(); + return taskFragment != null && taskFragment.canAffectSystemUiFlags() + && isVisible() && !mWaitForEnteringPinnedMode && !inPinnedWindowingMode(); } @@ -10384,7 +10388,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * game engines wait to get focus before drawing the content of the app. */ boolean shouldSendCompatFakeFocus() { - return mAppCompatController.getAppCompatFocusOverrides().shouldSendFakeFocus(); + return mAppCompatController.getFocusOverrides().shouldSendFakeFocus(); } boolean canCaptureSnapshot() { diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java index 26b7cc67876e..21628341ea62 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java @@ -100,19 +100,21 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord ActivitySnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue) { super(service); - mSnapshotPersistQueue = persistQueue; - mPersistInfoProvider = createPersistInfoProvider(service, - Environment::getDataSystemCeDirectory); - mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider); - mSnapshotLoader = new AppSnapshotLoader(mPersistInfoProvider); - initialize(new ActivitySnapshotCache()); - final boolean snapshotEnabled = !service.mContext .getResources() .getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots) && !ActivityManager.isLowRamDeviceStatic(); // Don't support Android Go setSnapshotEnabled(snapshotEnabled); + mSnapshotPersistQueue = persistQueue; + mPersistInfoProvider = createPersistInfoProvider(service, + Environment::getDataSystemCeDirectory); + mPersister = new TaskSnapshotPersister( + persistQueue, + mPersistInfoProvider, + shouldDisableSnapshots()); + mSnapshotLoader = new AppSnapshotLoader(mPersistInfoProvider); + initialize(new ActivitySnapshotCache()); } @Override diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 12c8f9ccac7c..906befc1edcc 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -1664,6 +1664,12 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { activityIdleInternal(null /* idleActivity */, false /* fromTimeout */, true /* processPausingActivities */, null /* configuration */); + if (rootTask.getParent() == null) { + // The activities in the task may already be finishing. Then the task could be removed + // when performing the idle check. + return; + } + // Reparent all the tasks to the bottom of the display final DisplayContent toDisplay = mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY); diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index 1a6c9a1a8797..a94f6252cd68 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -119,8 +119,8 @@ class AppCompatController { } @NonNull - AppCompatFocusOverrides getAppCompatFocusOverrides() { - return mAppCompatOverrides.getAppCompatFocusOverrides(); + AppCompatFocusOverrides getFocusOverrides() { + return mAppCompatOverrides.getFocusOverrides(); } @NonNull diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java index f8002a589eef..2d0ff9be2133 100644 --- a/services/core/java/com/android/server/wm/AppCompatOverrides.java +++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java @@ -33,7 +33,7 @@ public class AppCompatOverrides { @NonNull private final AppCompatAspectRatioOverrides mAspectRatioOverrides; @NonNull - private final AppCompatFocusOverrides mAppCompatFocusOverrides; + private final AppCompatFocusOverrides mFocusOverrides; @NonNull private final AppCompatResizeOverrides mResizeOverrides; @NonNull @@ -55,8 +55,8 @@ public class AppCompatOverrides { mAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord, appCompatConfiguration, optPropBuilder, appCompatDeviceStateQuery, mReachabilityOverrides); - mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord, - appCompatConfiguration, optPropBuilder); + mFocusOverrides = new AppCompatFocusOverrides(activityRecord, appCompatConfiguration, + optPropBuilder); mResizeOverrides = new AppCompatResizeOverrides(activityRecord, packageManager, optPropBuilder); mAppCompatLetterboxOverrides = new AppCompatLetterboxOverrides(activityRecord, @@ -79,8 +79,8 @@ public class AppCompatOverrides { } @NonNull - AppCompatFocusOverrides getAppCompatFocusOverrides() { - return mAppCompatFocusOverrides; + AppCompatFocusOverrides getFocusOverrides() { + return mFocusOverrides; } @NonNull diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index d32c31f1c1c7..5435d8f164da 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -193,7 +193,6 @@ import android.os.Message; import android.os.PowerManager; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; @@ -2187,12 +2186,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } - /** Returns {@code true} if the screen rotation animation needs to wait for the window. */ - boolean shouldSyncRotationChange(WindowState w) { - final AsyncRotationController controller = mAsyncRotationController; - return controller == null || !controller.isAsync(w); - } - void notifyInsetsChanged(Consumer<WindowState> dispatchInsetsChanged) { if (mFixedRotationLaunchingApp != null) { // The insets state of fixed rotation app is a rotated copy. Make sure the visibilities @@ -2279,10 +2272,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (!shellTransitions) { forAllWindows(w -> { w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly); - if (!rotateSeamlessly && w.mHasSurface) { - ProtoLog.v(WM_DEBUG_ORIENTATION, "Set mOrientationChanging of %s", w); - w.setOrientationChanging(true); - } }, true /* traverseTopToBottom */); mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation); if (!mDisplayRotation.hasSeamlessRotatingWindow()) { @@ -5083,15 +5072,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp Slog.w(TAG_WM, "Window freeze timeout expired."); mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT; - forAllWindows(w -> { - if (!w.getOrientationChanging()) { - return; - } - w.orientationChangeTimedOut(); - w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime() - - mWmService.mDisplayFreezeTime); - Slog.w(TAG_WM, "Force clearing orientation change: " + w); - }, true /* traverseTopToBottom */); mWmService.mWindowPlacerLocked.performSurfacePlacement(); } diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index db058cafe5fe..fa748d3a22a5 100644 --- a/services/core/java/com/android/server/wm/DragDropController.java +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -212,7 +212,7 @@ class DragDropController { surface = null; mDragState.mPid = callerPid; mDragState.mUid = callerUid; - mDragState.mOriginalAlpha = alpha; + mDragState.mStartDragAlpha = alpha; mDragState.mAnimatedScale = callingWin.mGlobalScale; mDragState.mToken = dragToken; mDragState.mStartDragDisplayContent = displayContent; @@ -287,7 +287,7 @@ class DragDropController { } final SurfaceControl.Transaction transaction = mDragState.mTransaction; - transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha); + transaction.setAlpha(surfaceControl, mDragState.mStartDragAlpha); transaction.show(surfaceControl); displayContent.reparentToOverlay(transaction, surfaceControl); mDragState.updateDragSurfaceLocked(true /* keepHandling */, diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index d48b9b4a5d10..69f32cb7b8ea 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -82,6 +82,7 @@ import java.util.concurrent.CompletableFuture; class DragState { private static final long MIN_ANIMATION_DURATION_MS = 195; private static final long MAX_ANIMATION_DURATION_MS = 375; + private static final float DIFFERENT_DISPLAY_RETURN_ANIMATION_SCALE = 0.75f; private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ | View.DRAG_FLAG_GLOBAL_URI_WRITE; @@ -114,8 +115,9 @@ class DragState { boolean mDragResult; boolean mRelinquishDragSurfaceToDropTarget; float mAnimatedScale = 1.0f; - float mOriginalAlpha; - float mOriginalDisplayX, mOriginalDisplayY; + float mStartDragAlpha; + // Coords are in display coordinates space. + float mStartDragDisplayX, mStartDragDisplayY; float mCurrentDisplayX, mCurrentDisplayY; float mThumbOffsetX, mThumbOffsetY; InputInterceptor mInputInterceptor; @@ -497,8 +499,8 @@ class DragState { */ void broadcastDragStartedLocked(final float touchX, final float touchY) { Trace.instant(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DRAG_STARTED"); - mOriginalDisplayX = mCurrentDisplayX = touchX; - mOriginalDisplayY = mCurrentDisplayY = touchY; + mStartDragDisplayX = mCurrentDisplayX = touchX; + mStartDragDisplayY = mCurrentDisplayY = touchY; // Cache a base-class instance of the clip metadata so that parceling // works correctly in calling out to the apps. @@ -809,21 +811,32 @@ class DragState { mCurrentDisplayY), PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale, mAnimatedScale), - PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f)); + PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0f)); + duration = MIN_ANIMATION_DURATION_MS; + } else if (Flags.enableConnectedDisplaysDnd() && mCurrentDisplayContent.getDisplayId() + != mStartDragDisplayContent.getDisplayId()) { + animator = ValueAnimator.ofPropertyValuesHolder( + PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, + mCurrentDisplayX - mThumbOffsetX, mCurrentDisplayX - mThumbOffsetX), + PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, + mCurrentDisplayY - mThumbOffsetY, mCurrentDisplayY - mThumbOffsetY), + PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale, + DIFFERENT_DISPLAY_RETURN_ANIMATION_SCALE * mAnimatedScale), + PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0f)); duration = MIN_ANIMATION_DURATION_MS; } else { animator = ValueAnimator.ofPropertyValuesHolder( PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, - mCurrentDisplayX - mThumbOffsetX, mOriginalDisplayX - mThumbOffsetX), + mCurrentDisplayX - mThumbOffsetX, mStartDragDisplayX - mThumbOffsetX), PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, - mCurrentDisplayY - mThumbOffsetY, mOriginalDisplayY - mThumbOffsetY), + mCurrentDisplayY - mThumbOffsetY, mStartDragDisplayY - mThumbOffsetY), PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale, mAnimatedScale), - PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, - mOriginalAlpha / 2)); + PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, + mStartDragAlpha / 2)); - final float translateX = mOriginalDisplayX - mCurrentDisplayX; - final float translateY = mOriginalDisplayY - mCurrentDisplayY; + final float translateX = mStartDragDisplayX - mCurrentDisplayX; + final float translateY = mStartDragDisplayY - mCurrentDisplayY; // Adjust the duration to the travel distance. final double travelDistance = Math.sqrt( translateX * translateX + translateY * translateY); @@ -853,7 +866,7 @@ class DragState { mCurrentDisplayY), PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale, mAnimatedScale), - PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f)); + PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0f)); } else { animator = ValueAnimator.ofPropertyValuesHolder( PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, @@ -861,7 +874,7 @@ class DragState { PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, mCurrentDisplayY - mThumbOffsetY, mCurrentDisplayY), PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale, 0), - PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0)); + PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0)); } final AnimationListener listener = new AnimationListener(); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 04f09d5fe627..7a3eb67bf94e 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -205,7 +205,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // For seamless rotation cases this always stays true, as the windows complete their orientation // changes 1 by 1 without disturbing global state. boolean mOrientationChangeComplete = true; - boolean mWallpaperActionPending = false; private final Handler mHandler; @@ -1100,10 +1099,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } - if ((bulkUpdateParams & SET_WALLPAPER_ACTION_PENDING) != 0) { - mWallpaperActionPending = true; - } - return doRequest; } diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java index 3eb13c52cca6..5e1d7928e96d 100644 --- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java +++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java @@ -231,8 +231,13 @@ class SnapshotPersistQueue { if (next.isReady(mUserManagerInternal)) { isReadyToWrite = true; next.onDequeuedLocked(); - } else { + } else if (!mShutdown) { mWriteQueue.addLast(next); + } else { + // User manager is locked and device is shutting down, skip writing + // this item. + next.onDequeuedLocked(); + next = null; } } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index fe478c60bc32..295759c2fc7e 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -468,9 +468,6 @@ class Task extends TaskFragment { // NOTE: This value needs to be persisted with each task private TaskDescription mTaskDescription; - /** @see #setCanAffectSystemUiFlags */ - private boolean mCanAffectSystemUiFlags = true; - private static Exception sTmpException; private boolean mForceShowForAllUsers; @@ -3288,21 +3285,6 @@ class Task extends TaskFragment { return isRootTask() && callback.test(this) ? this : null; } - /** - * @param canAffectSystemUiFlags If false, all windows in this task can not affect SystemUI - * flags. See {@link WindowState#canAffectSystemUiFlags()}. - */ - void setCanAffectSystemUiFlags(boolean canAffectSystemUiFlags) { - mCanAffectSystemUiFlags = canAffectSystemUiFlags; - } - - /** - * @see #setCanAffectSystemUiFlags - */ - boolean canAffectSystemUiFlags() { - return mCanAffectSystemUiFlags; - } - void dontAnimateDimExit() { mDimmer.dontAnimateExit(); } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index a031acad638f..1993053c16cd 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -409,6 +409,9 @@ class TaskFragment extends WindowContainer<WindowContainer> { private boolean mForceTranslucent = false; + /** @see #setCanAffectSystemUiFlags */ + private boolean mCanAffectSystemUiFlags = true; + final Point mLastSurfaceSize = new Point(); private final Rect mTmpBounds = new Rect(); @@ -967,6 +970,27 @@ class TaskFragment extends WindowContainer<WindowContainer> { } /** + * @param canAffectSystemUiFlags If false, all windows in this taskfragment can not affect + * SystemUI flags. See + * {@link WindowState#canAffectSystemUiFlags()}. + */ + void setCanAffectSystemUiFlags(boolean canAffectSystemUiFlags) { + mCanAffectSystemUiFlags = canAffectSystemUiFlags; + } + + /** + * @see #setCanAffectSystemUiFlags + */ + boolean canAffectSystemUiFlags() { + if (!mCanAffectSystemUiFlags) { + return false; + } + final TaskFragment parentTaskFragment = + getParent() != null ? getParent().asTaskFragment() : null; + return parentTaskFragment == null || parentTaskFragment.canAffectSystemUiFlags(); + } + + /** * Returns the TaskFragment that is being organized, which could be this or the ascendant * TaskFragment. */ diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java index d89dc0b8e81c..91cd9498a356 100644 --- a/services/core/java/com/android/server/wm/TaskPersister.java +++ b/services/core/java/com/android/server/wm/TaskPersister.java @@ -245,18 +245,20 @@ public class TaskPersister implements PersisterQueue.Listener { private static String fileToString(File file) { final String newline = System.lineSeparator(); + BufferedReader reader = null; try { - BufferedReader reader = new BufferedReader(new FileReader(file)); + reader = new BufferedReader(new FileReader(file)); StringBuffer sb = new StringBuffer((int) file.length() * 2); String line; while ((line = reader.readLine()) != null) { sb.append(line + newline); } - reader.close(); return sb.toString(); } catch (IOException ioe) { Slog.e(TAG, "Couldn't read file " + file.getName()); return null; + } finally { + IoUtils.closeQuietly(reader); } } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 7d300e98f44b..432ed1d0b61d 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -66,16 +66,19 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot TaskSnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue) { super(service); - mPersistInfoProvider = createPersistInfoProvider(service, - Environment::getDataSystemCeDirectory); - mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider); - - initialize(new TaskSnapshotCache(new AppSnapshotLoader(mPersistInfoProvider))); final boolean snapshotEnabled = !service.mContext .getResources() .getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots); setSnapshotEnabled(snapshotEnabled); + mPersistInfoProvider = createPersistInfoProvider(service, + Environment::getDataSystemCeDirectory); + + mPersister = new TaskSnapshotPersister( + persistQueue, + mPersistInfoProvider, + shouldDisableSnapshots()); + initialize(new TaskSnapshotCache(new AppSnapshotLoader(mPersistInfoProvider))); } static PersistInfoProvider createPersistInfoProvider(WindowManagerService service, diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java index 87be74ae1dd9..538fd8dc5406 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java @@ -26,6 +26,7 @@ import android.window.TaskSnapshot; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.UserManagerInternal; +import com.android.window.flags.Flags; import java.io.File; import java.util.Arrays; @@ -37,6 +38,8 @@ import java.util.Arrays; */ class TaskSnapshotPersister extends BaseAppSnapshotPersister { + private final boolean mDisableSnapshots; + /** * The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was * called. @@ -45,8 +48,10 @@ class TaskSnapshotPersister extends BaseAppSnapshotPersister { private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>(); TaskSnapshotPersister(SnapshotPersistQueue persistQueue, - PersistInfoProvider persistInfoProvider) { + PersistInfoProvider persistInfoProvider, + boolean disableSnapshots) { super(persistQueue, persistInfoProvider); + mDisableSnapshots = Flags.checkDisabledSnapshotsInTaskPersister() && disableSnapshots; } /** @@ -57,6 +62,9 @@ class TaskSnapshotPersister extends BaseAppSnapshotPersister { * @param snapshot The snapshot to persist. */ void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) { + if (mDisableSnapshots) { + return; + } synchronized (mLock) { mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId); super.persistSnapshot(taskId, userId, snapshot); @@ -71,6 +79,9 @@ class TaskSnapshotPersister extends BaseAppSnapshotPersister { */ @Override void removeSnapshot(int taskId, int userId) { + if (mDisableSnapshots) { + return; + } synchronized (mLock) { mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId); super.removeSnapshot(taskId, userId); @@ -86,7 +97,7 @@ class TaskSnapshotPersister extends BaseAppSnapshotPersister { * model. */ void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) { - if (runningUserIds.length == 0) { + if (runningUserIds.length == 0 || mDisableSnapshots) { return; } synchronized (mLock) { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index f4a455a9c2dd..75cefdff2b0b 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2890,6 +2890,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { leashReference = leashReference.getParent(); } } + if (wc == leashReference + && sortedTargets.get(i).mWindowingMode == WINDOWING_MODE_PINNED) { + // If a PiP task is the only target, we wanna make sure the transition root leash + // is at the top in case PiP is sent to back. This is done because a pinned task is + // meant to be always-on-top throughout a transition. + leashReference = ancestor.getTopChild(); + } final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName( "Transition Root: " + leashReference.getName()) .setCallsite("Transition.calculateTransitionRoots").build(); diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java index 7ef8d8d0c16a..df70ed2e99a8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerFlags.java +++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java @@ -58,6 +58,8 @@ class WindowManagerFlags { final boolean mEnsureWallpaperInTransitions; + final boolean mAodTransition = Flags.aodTransition(); + /* End Available Flags */ WindowManagerFlags() { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index dd6e15b74a58..bf23e757fee5 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -6407,11 +6407,6 @@ public class WindowManagerService extends IWindowManager.Stub if (mFrozenDisplayId != INVALID_DISPLAY && mFrozenDisplayId == w.getDisplayId() && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) { ProtoLog.v(WM_DEBUG_ORIENTATION, "Changing surface while display frozen: %s", w); - // WindowsState#reportResized won't tell invisible requested window to redraw, - // so do not set it as changing orientation to avoid affecting draw state. - if (w.isVisibleRequested()) { - w.setOrientationChanging(true); - } if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) { mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE; // XXX should probably keep timeout from diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index f0f1b2e47cc8..3c3a180f4da1 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -38,6 +38,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_ import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS; import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED; import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK; @@ -1862,6 +1863,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub taskFragment.setPinned(pinned); break; } + case OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS: { + taskFragment.setCanAffectSystemUiFlags(operation.getBooleanValue()); + + // Request to apply the flags. + mService.mWindowManager.mWindowPlacerLocked.requestTraversal(); + break; + } } return effects; } @@ -1937,6 +1945,16 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return false; } + if ((opType == OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS) + && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { + final Throwable exception = new SecurityException( + "Only a system organizer can perform OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS." + ); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment, + opType, exception); + return false; + } + final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken(); return secondaryFragmentToken == null || validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType, diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 85e3d89730a3..da58470edc1e 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -148,7 +148,6 @@ import static com.android.server.wm.WindowManagerService.MY_PID; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; -import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT; import static com.android.server.wm.WindowStateAnimator.COMMIT_DRAW_PENDING; import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING; import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN; @@ -592,27 +591,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP /** Completely remove from window manager after exit animation? */ boolean mRemoveOnExit; - /** - * Set when the orientation is changing and this window has not yet - * been updated for the new orientation. - */ - private boolean mOrientationChanging; - /** The time when the window was last requested to redraw for orientation change. */ private long mOrientationChangeRedrawRequestTime; /** - * Sometimes in addition to the mOrientationChanging - * flag we report that the orientation is changing - * due to a mismatch in current and reported configuration. - * - * In the case of timeout we still need to make sure we - * leave the orientation changing state though, so we - * use this as a special time out escape hatch. - */ - private boolean mOrientationChangeTimedOut; - - /** * The orientation during the last visible call to relayout. If our * current orientation is different, the window can't be ready * to be shown. @@ -1497,8 +1479,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Reset the drawn state if the window need to redraw for the change, so the transition // can wait until it has finished drawing to start. - if ((configChanged || getOrientationChanging() || dragResizingChanged) - && isVisibleRequested()) { + if ((configChanged || dragResizingChanged) && isVisibleRequested()) { winAnimator.mDrawState = DRAW_PENDING; if (mActivityRecord != null) { mActivityRecord.clearAllDrawn(); @@ -1512,15 +1493,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP ProtoLog.v(WM_DEBUG_RESIZE, "Resizing window %s", this); mWmService.mResizingWindows.add(this); } - } else if (getOrientationChanging()) { - if (isDrawn()) { - ProtoLog.v(WM_DEBUG_ORIENTATION, - "Orientation not waiting for draw in %s, surfaceController %s", this, - winAnimator.mSurfaceControl); - setOrientationChanging(false); - mLastFreezeDuration = (int)(SystemClock.elapsedRealtime() - - mWmService.mDisplayFreezeTime); - } } } @@ -1528,46 +1500,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return !mWindowFrames.mFrame.equals(mWindowFrames.mLastFrame); } - boolean getOrientationChanging() { - if (mTransitionController.isShellTransitionsEnabled()) { - // Shell transition doesn't use the methods for display frozen state. - return false; - } - // In addition to the local state flag, we must also consider the difference in the last - // reported configuration vs. the current state. If the client code has not been informed of - // the change, logic dependent on having finished processing the orientation, such as - // unfreezing, could be improperly triggered. - // TODO(b/62846907): Checking against {@link mLastReportedConfiguration} could be flaky as - // this is not necessarily what the client has processed yet. Find a - // better indicator consistent with the client. - return (mOrientationChanging || (isVisible() - && getConfiguration().orientation != getLastReportedConfiguration().orientation)) - && !mSeamlesslyRotated - && !mOrientationChangeTimedOut; - } - - void setOrientationChanging(boolean changing) { - mOrientationChangeTimedOut = false; - if (mOrientationChanging == changing) { - return; - } - mOrientationChanging = changing; - if (changing) { - mLastFreezeDuration = 0; - if (mWmService.mRoot.mOrientationChangeComplete - && mDisplayContent.shouldSyncRotationChange(this)) { - mWmService.mRoot.mOrientationChangeComplete = false; - } - } else { - // The orientation change is completed. If it was hidden by the animation, reshow it. - mDisplayContent.finishAsyncRotation(mToken); - } - } - - void orientationChangeTimedOut() { - mOrientationChangeTimedOut = true; - } - @Override void onDisplayChanged(DisplayContent dc) { if (dc != null && mDisplayContent != null && dc != mDisplayContent @@ -3355,12 +3287,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mAppFreezing = false; - if (mHasSurface && !getOrientationChanging() - && mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) { - ProtoLog.v(WM_DEBUG_ORIENTATION, - "set mOrientationChanging of %s", this); - setOrientationChanging(true); - } mLastFreezeDuration = 0; setDisplayLayoutNeeded(); return true; @@ -4266,9 +4192,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + " mDestroying=" + mDestroying + " mRemoved=" + mRemoved); } - if (getOrientationChanging() || mAppFreezing) { - pw.println(prefix + "mOrientationChanging=" + mOrientationChanging - + " configOrientationChanging=" + if (mAppFreezing) { + pw.println(prefix + " configOrientationChanging=" + (getLastReportedConfiguration().orientation != getConfiguration().orientation) + " mAppFreezing=" + mAppFreezing); } @@ -5356,7 +5281,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Send information to SurfaceFlinger about the priority of the current window. updateFrameRateSelectionPriorityIfNeeded(); updateScaleIfNeeded(); - mWinAnimator.prepareSurfaceLocked(getSyncTransaction()); + mWinAnimator.prepareSurfaceLocked(getPendingTransaction()); applyDims(); } super.prepareSurfaces(); diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index d973fb014e35..298580e4bb81 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -29,7 +29,6 @@ import static android.view.WindowManager.TRANSIT_OLD_NONE; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_DRAW; -import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STARTING_WINDOW; import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC; import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS; @@ -417,56 +416,19 @@ class WindowStateAnimator { } } - void computeShownFrameLocked() { - if (mWin.mIsWallpaper && mService.mRoot.mWallpaperActionPending) { - return; - } else if (mWin.isDragResizeChanged()) { - // This window is awaiting a relayout because user just started (or ended) - // drag-resizing. The shown frame (which affects surface size and pos) - // should not be updated until we get next finished draw with the new surface. - // Otherwise one or two frames rendered with old settings would be displayed - // with new geometry. - return; - } - - if (DEBUG) { - Slog.v(TAG, "computeShownFrameLocked: " + this - + " not attached, mAlpha=" + mAlpha); - } - - mShownAlpha = mAlpha; - } - void prepareSurfaceLocked(SurfaceControl.Transaction t) { final WindowState w = mWin; if (!hasSurface()) { - - // There is no need to wait for an animation change if our window is gone for layout - // already as we'll never be visible. - if (w.getOrientationChanging() && w.isGoneForLayout()) { - ProtoLog.v(WM_DEBUG_ORIENTATION, "Orientation change skips hidden %s", w); - w.setOrientationChanging(false); - } return; } - computeShownFrameLocked(); + mShownAlpha = mAlpha; if (!w.isOnScreen()) { hide(t, "prepareSurfaceLocked"); if (!w.mIsWallpaper || !mService.mFlags.mEnsureWallpaperInTransitions) { mWallpaperControllerLocked.hideWallpapers(w); } - - // If we are waiting for this window to handle an orientation change. If this window is - // really hidden (gone for layout), there is no point in still waiting for it. - // Note that this does introduce a potential glitch if the window becomes unhidden - // before it has drawn for the new orientation. - if (w.getOrientationChanging() && w.isGoneForLayout()) { - w.setOrientationChanging(false); - ProtoLog.v(WM_DEBUG_ORIENTATION, - "Orientation change skips hidden %s", w); - } } else if (mLastAlpha != mShownAlpha || mLastHidden) { mLastAlpha = mShownAlpha; @@ -483,20 +445,6 @@ class WindowStateAnimator { } } } - - if (w.getOrientationChanging()) { - if (!w.isDrawn()) { - if (w.mDisplayContent.shouldSyncRotationChange(w)) { - w.mWmService.mRoot.mOrientationChangeComplete = false; - mAnimator.mLastWindowFreezeSource = w; - } - ProtoLog.v(WM_DEBUG_ORIENTATION, - "Orientation continue waiting for draw in %s", w); - } else { - w.setOrientationChanging(false); - ProtoLog.v(WM_DEBUG_ORIENTATION, "Orientation change complete in %s", w); - } - } } private void showRobustly(SurfaceControl.Transaction t) { diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index dff718a4b7d5..a34b5115faf9 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -127,7 +127,6 @@ class WindowSurfacePlacer { mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement); loopCount--; } while (mTraversalScheduled && loopCount > 0); - mService.mRoot.mWallpaperActionPending = false; } private void performSurfacePlacementLoop() { diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java index ce4126a425c6..55d23585fb70 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java @@ -34,8 +34,10 @@ import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.DisplayManager; import android.os.Handler; +import android.os.PowerManager; import android.util.ArraySet; import android.util.Dumpable; +import android.util.Slog; import android.view.Display; import android.view.DisplayInfo; import android.view.Surface; @@ -44,6 +46,7 @@ import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScr import com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle; import com.android.server.policy.BookStylePreferredScreenCalculator.StateTransition; import com.android.server.policy.BookStyleClosedStatePredicate.ConditionSensorListener.SensorSubscription; +import com.android.server.policy.feature.flags.FeatureFlags; import java.io.PrintWriter; import java.util.ArrayList; @@ -62,12 +65,19 @@ import java.util.function.Supplier; public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceStateProvider>, DisplayManager.DisplayListener, Dumpable { + private static final String TAG = "BookStyleClosedStatePredicate"; + private final BookStylePreferredScreenCalculator mClosedStateCalculator; private final Handler mHandler = new Handler(); private final PostureEstimator mPostureEstimator; private final DisplayManager mDisplayManager; + private final PowerManager mPowerManager; + private final FeatureFlags mFeatureFlags; private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); + @PowerManager.ScreenTimeoutPolicy + private volatile int mScreenTimeoutPolicy; + /** * Creates {@link BookStyleClosedStatePredicate}. It is expected that the device has a pair * of accelerometer sensors (one for each movable part of the device), see parameter @@ -92,8 +102,11 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt public BookStyleClosedStatePredicate(@NonNull Context context, @NonNull ClosedStateUpdatesListener updatesListener, @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor, - @NonNull List<StateTransition> stateTransitions) { + @NonNull List<StateTransition> stateTransitions, + @NonNull FeatureFlags featureFlags) { + mFeatureFlags = featureFlags; mDisplayManager = context.getSystemService(DisplayManager.class); + mPowerManager = context.getSystemService(PowerManager.class); mDisplayManager.registerDisplayListener(this, mHandler); mClosedStateCalculator = new BookStylePreferredScreenCalculator(stateTransitions); @@ -108,6 +121,23 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt } /** + * Initialize the predicate, the predicate could subscribe to various data sources the data + * from which could be used later when calling {@link BookStyleClosedStatePredicate#test}. + */ + public void init() { + if (mFeatureFlags.forceFoldablesTentModeWithScreenWakelock()) { + try { + mPowerManager.addScreenTimeoutPolicyListener(DEFAULT_DISPLAY, Runnable::run, + new ScreenTimeoutPolicyListener()); + } catch (IllegalStateException exception) { + // TODO: b/389613319 - remove after removing the screen timeout policy API flagging + Slog.e(TAG, "Error subscribing to the screen timeout policy changes"); + exception.printStackTrace(); + } + } + } + + /** * Based on the current sensor readings and current state, returns true if the device should use * 'CLOSED' device state and false if it should not use 'CLOSED' state (e.g. could use half-open * or open states). @@ -119,13 +149,24 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt mPostureEstimator.onDeviceClosedStatusChanged(hingeAngle == ANGLE_0); + final boolean isLikelyTentOrWedgeMode = mPostureEstimator.isLikelyTentOrWedgeMode() + || shouldForceTentOrWedgeMode(); + final PreferredScreen preferredScreen = mClosedStateCalculator. - calculatePreferredScreen(hingeAngle, mPostureEstimator.isLikelyTentOrWedgeMode(), + calculatePreferredScreen(hingeAngle, isLikelyTentOrWedgeMode, mPostureEstimator.isLikelyReverseWedgeMode(hingeAngle)); return preferredScreen == OUTER; } + private boolean shouldForceTentOrWedgeMode() { + if (!mFeatureFlags.forceFoldablesTentModeWithScreenWakelock()) { + return false; + } + + return mScreenTimeoutPolicy == PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON; + } + private HingeAngle hingeAngleFromFloat(float hingeAngle) { if (hingeAngle == 0f) { return ANGLE_0; @@ -163,7 +204,7 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt @Override public void dump(@NonNull PrintWriter writer, @Nullable String[] args) { writer.println(" " + getDumpableName()); - + writer.println(" mScreenTimeoutPolicy=" + mScreenTimeoutPolicy); mPostureEstimator.dump(writer, args); mClosedStateCalculator.dump(writer, args); } @@ -172,6 +213,15 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt void onClosedStateUpdated(); } + private class ScreenTimeoutPolicyListener implements + PowerManager.ScreenTimeoutPolicyListener { + @Override + public void onScreenTimeoutPolicyChanged(int screenTimeoutPolicy) { + // called from the binder thread + mScreenTimeoutPolicy = screenTimeoutPolicy; + } + } + /** * Estimates if the device is going to enter wedge/tent mode based on the sensor data */ diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java index f34ec72d7e27..dfc4ba2cfa71 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java @@ -114,7 +114,7 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking(); final DeviceStatePredicateWrapper[] configuration = createConfiguration( - leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees); + leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees, featureFlags); mProvider = new FoldableDeviceStateProvider(mContext, sensorManager, hingeAngleSensor, hallSensor, displayManager, configuration); @@ -122,10 +122,10 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements private DeviceStatePredicateWrapper[] createConfiguration( @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor, - Integer closeAngleDegrees) { + Integer closeAngleDegrees, @NonNull FeatureFlags featureFlags) { return new DeviceStatePredicateWrapper[]{ createClosedConfiguration(leftAccelerometerSensor, rightAccelerometerSensor, - closeAngleDegrees), + closeAngleDegrees, featureFlags), createConfig(getHalfOpenedDeviceState(), /* activeStatePredicate= */ (provider) -> { final float hingeAngle = provider.getHingeAngle(); @@ -147,7 +147,7 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements private DeviceStatePredicateWrapper createClosedConfiguration( @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor, - @Nullable Integer closeAngleDegrees) { + @Nullable Integer closeAngleDegrees, @NonNull FeatureFlags featureFlags) { if (closeAngleDegrees != null) { // Switch displays at closeAngleDegrees in both ways (folding and unfolding) @@ -161,9 +161,12 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements if (mEnablePostureBasedClosedState) { // Use smart closed state predicate that will use different switch angles // based on the device posture (e.g. wedge mode, tent mode, reverse wedge mode) - return createConfig(getClosedDeviceState(), /* activeStatePredicate= */ - new BookStyleClosedStatePredicate(mContext, this, leftAccelerometerSensor, - rightAccelerometerSensor, DEFAULT_STATE_TRANSITIONS)); + final BookStyleClosedStatePredicate predicate = new BookStyleClosedStatePredicate( + mContext, this, leftAccelerometerSensor, rightAccelerometerSensor, + DEFAULT_STATE_TRANSITIONS, featureFlags); + return createConfig(getClosedDeviceState(), + /* activeStatePredicate= */ predicate, + /* initializer= */ predicate::init); } // Switch to the outer display only at 0 degrees but use TENT_MODE_SWITCH_ANGLE_DEGREES diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java index daeaa9833d78..8e749529978e 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java @@ -200,6 +200,16 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, } } + @Override + public void onSystemReady() { + for (int i = 0; i < mConfigurations.length; i++) { + final DeviceStatePredicateWrapper configuration = mConfigurations[i]; + if (configuration.mInitializer != null) { + configuration.mInitializer.run(); + } + } + } + private void assertUniqueDeviceStateIdentifier(DeviceStatePredicateWrapper configuration) { if (mStateConditions.get(configuration.mDeviceState.getIdentifier()) != null) { throw new IllegalArgumentException("Device state configurations must have unique" @@ -461,11 +471,12 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, private final DeviceState mDeviceState; private final Predicate<FoldableDeviceStateProvider> mActiveStatePredicate; private final Predicate<FoldableDeviceStateProvider> mAvailabilityPredicate; + private final Runnable mInitializer; private DeviceStatePredicateWrapper( @NonNull DeviceState deviceState, @NonNull Predicate<FoldableDeviceStateProvider> predicate) { - this(deviceState, predicate, ALLOWED); + this(deviceState, predicate, ALLOWED, /* initializer= */ null); } /** Create a configuration with availability and availability predicate **/ @@ -473,10 +484,28 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, @NonNull DeviceState deviceState, @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate, @NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate) { + this(deviceState, activeStatePredicate, availabilityPredicate, /* initializer= */ null); + } + + /** + * Create a configuration with availability and availability predicate. + * @param deviceState specifies device state for this configuration + * @param activeStatePredicate predicate that should return 'true' when this device state + * wants to be and can be active + * @param availabilityPredicate predicate that should return 'true' only when this device + * state is allowed + * @param initializer callback that will be called when the system is booted and ready + */ + private DeviceStatePredicateWrapper( + @NonNull DeviceState deviceState, + @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate, + @NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate, + @Nullable Runnable initializer) { mDeviceState = deviceState; mActiveStatePredicate = activeStatePredicate; mAvailabilityPredicate = availabilityPredicate; + mInitializer = initializer; } /** Create a configuration with an active state predicate **/ @@ -487,6 +516,16 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, return new DeviceStatePredicateWrapper(deviceState, activeStatePredicate); } + /** Create a configuration with an active state predicate and an initializer **/ + public static DeviceStatePredicateWrapper createConfig( + @NonNull DeviceState deviceState, + @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate, + @Nullable Runnable initializer + ) { + return new DeviceStatePredicateWrapper(deviceState, activeStatePredicate, ALLOWED, + initializer); + } + /** Create a configuration with availability and active state predicate **/ public static DeviceStatePredicateWrapper createConfig( @NonNull DeviceState deviceState, diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig index 21e33dd1b99a..da2e5ee37c1a 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig @@ -9,6 +9,16 @@ flag { } flag { + name: "force_foldables_tent_mode_with_screen_wakelock" + namespace: "windowing_frontend" + description: "Switching displays on a foldable device later if screen wakelock is present" + bug: "363174979" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "enable_foldables_posture_based_closed_state" namespace: "windowing_frontend" description: "Enables smarter closed device state state for foldable devices" diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java index 2d725d1e2294..c25d5ef09209 100644 --- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java +++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java @@ -45,6 +45,8 @@ import android.hardware.SensorManager; import android.hardware.display.DisplayManager; import android.hardware.input.InputSensorInfo; import android.os.Handler; +import android.os.PowerManager; +import android.os.PowerManager.ScreenTimeoutPolicyListener; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.view.Display; @@ -98,6 +100,8 @@ public final class BookStyleDeviceStatePolicyTest { @Mock DisplayManager mDisplayManager; @Mock + PowerManager mPowerManager; + @Mock private Display mDisplay; private final FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl(); @@ -118,6 +122,7 @@ public final class BookStyleDeviceStatePolicyTest { private Map<Sensor, List<SensorEventListener>> mSensorEventListeners = new HashMap<>(); private DeviceStateProvider mProvider; + private BookStyleDeviceStatePolicy mPolicy; @Before public void setup() { @@ -125,6 +130,7 @@ public final class BookStyleDeviceStatePolicyTest { mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_FOLDABLES_POSTURE_BASED_CLOSED_STATE, true); mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_DUAL_DISPLAY_BLOCKING, true); + mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true); when(mInputSensorInfo.getName()).thenReturn("hall-effect"); mHallSensor = new Sensor(mInputSensorInfo); @@ -146,6 +152,7 @@ public final class BookStyleDeviceStatePolicyTest { when(mDisplayManager.getDisplay(eq(DEFAULT_DISPLAY))).thenReturn(mDisplay); mContext.addMockSystemService(DisplayManager.class, mDisplayManager); + mContext.addMockSystemService(PowerManager.class, mPowerManager); mContext.ensureTestableResources(); when(mContext.getResources().getConfiguration()).thenReturn(mConfiguration); @@ -592,6 +599,62 @@ public final class BookStyleDeviceStatePolicyTest { } @Test + public void test_unfoldTo85Degrees_screenWakeLockExists_forceTentModeWithWakeLockEnabled() + throws Exception { + mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true); + mInstrumentation.runOnMainSync(() -> mProvider = createProvider()); + mPolicy.getDeviceStateProvider().onSystemReady(); + sendHingeAngle(0f); + final ScreenTimeoutPolicyListener listener = captureScreenTimeoutPolicyListener(); + listener.onScreenTimeoutPolicyChanged(PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_CLOSED); + sendHingeAngle(180f); + assertLatestReportedState(DEVICE_STATE_OPENED); + sendHingeAngle(0f); + sendHingeAngle(15f); + assertLatestReportedState(DEVICE_STATE_CLOSED); + + sendHingeAngle(85f); + + // Keeps 'closed' state meaning that it is in 'tent' mode as we have a screen wakelock + assertLatestReportedState(DEVICE_STATE_CLOSED); + } + + @Test + public void test_unfoldTo85Degrees_noScreenWakelock_forceTentModeWithWakeLockEnabled() + throws Exception { + mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true); + mInstrumentation.runOnMainSync(() -> mProvider = createProvider()); + mPolicy.getDeviceStateProvider().onSystemReady(); + sendHingeAngle(0f); + final ScreenTimeoutPolicyListener listener = captureScreenTimeoutPolicyListener(); + listener.onScreenTimeoutPolicyChanged(PowerManager.SCREEN_TIMEOUT_ACTIVE); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_CLOSED); + sendHingeAngle(180f); + assertLatestReportedState(DEVICE_STATE_OPENED); + sendHingeAngle(0f); + assertLatestReportedState(DEVICE_STATE_CLOSED); + + sendHingeAngle(85f); + + // Switches to half-opened state as we don't have a screen wakelock + assertLatestReportedState(DEVICE_STATE_HALF_OPENED); + } + + @Test + public void test_unfoldTo85Degrees_notSubscribedToWakeLocks_forceTentModeWithWakeLockDisabled() + throws Exception { + mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, false); + mInstrumentation.runOnMainSync(() -> mProvider = createProvider()); + + mPolicy.getDeviceStateProvider().onSystemReady(); + + verify(mPowerManager, never()).addScreenTimeoutPolicyListener(anyInt(), any(), any()); + } + + @Test public void test_foldTo10_leftSideIsFlat_keepsInnerScreenForReverseWedge() { sendHingeAngle(180f); sendLeftSideFlatSensorEvent(true); @@ -751,8 +814,17 @@ public final class BookStyleDeviceStatePolicyTest { } private DeviceStateProvider createProvider() { - return new BookStyleDeviceStatePolicy(mFakeFeatureFlags, mContext, mHingeAngleSensor, + mPolicy = new BookStyleDeviceStatePolicy(mFakeFeatureFlags, mContext, mHingeAngleSensor, mHallSensor, mLeftAccelerometer, mRightAccelerometer, - /* closeAngleDegrees= */ null).getDeviceStateProvider(); + /* closeAngleDegrees= */ null); + return mPolicy.getDeviceStateProvider(); + } + + private ScreenTimeoutPolicyListener captureScreenTimeoutPolicyListener() { + final ArgumentCaptor<ScreenTimeoutPolicyListener> captor = ArgumentCaptor + .forClass(ScreenTimeoutPolicyListener.class); + verify(mPowerManager, atLeastOnce()) + .addScreenTimeoutPolicyListener(anyInt(), any(), captor.capture()); + return captor.getValue(); } } diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java index 58e4b9177808..8f26bf32335a 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java @@ -72,8 +72,6 @@ public class BroadcastHelperTest { private static final String PACKAGE_CHANGED_TEST_PACKAGE_NAME = "testpackagename"; private static final String PACKAGE_CHANGED_TEST_MAIN_ACTIVITY = PACKAGE_CHANGED_TEST_PACKAGE_NAME + ".MainActivity"; - private static final String PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED = - "android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"; @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @@ -125,34 +123,23 @@ public class BroadcastHelperTest { @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES) @Test - public void changeNonExportedComponent_sendPackageChangedBroadcastToSystem_withPermission() + public void changeNonExportedComponent_sendPackageChangedBroadcastToSystemAndApplicationItself() throws Exception { changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */, new String[0] /* sharedPackages */); - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mMockActivityManagerInternal).broadcastIntentWithCallback( - captor.capture(), eq(null), - eq(new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED}), - anyInt(), eq(null), eq(null), eq(null)); - Intent intent = captor.getValue(); - assertNotNull(intent); - assertThat(intent.getPackage()).isEqualTo("android"); - } + ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class); + verify(mMockActivityManagerInternal, times(2)).broadcastIntentWithCallback( + captorIntent.capture(), eq(null), eq(null), anyInt(), eq(null), eq(null), eq(null)); + List<Intent> intents = captorIntent.getAllValues(); + assertNotNull(intents); + assertThat(intents.size()).isEqualTo(2); - @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES) - @Test - public void changeNonExportedComponent_sendPackageChangedBroadcastToApplicationItself() - throws Exception { - changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */, - new String[0] /* sharedPackages */); + final Intent intent1 = intents.get(0); + assertThat(intent1.getPackage()).isEqualTo("android"); - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null), - eq(null), anyInt(), eq(null), eq(null), eq(null)); - Intent intent = captor.getValue(); - assertNotNull(intent); - assertThat(intent.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME); + final Intent intent2 = intents.get(1); + assertThat(intent2.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME); } @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES) @@ -163,31 +150,20 @@ public class BroadcastHelperTest { new String[]{"shared.package"} /* sharedPackages */); ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class); - ArgumentCaptor<String[]> captorRequiredPermissions = ArgumentCaptor.forClass( - String[].class); verify(mMockActivityManagerInternal, times(3)).broadcastIntentWithCallback( - captorIntent.capture(), eq(null), captorRequiredPermissions.capture(), anyInt(), - eq(null), eq(null), eq(null)); + captorIntent.capture(), eq(null), eq(null), anyInt(), eq(null), eq(null), eq(null)); List<Intent> intents = captorIntent.getAllValues(); - List<String[]> requiredPermissions = captorRequiredPermissions.getAllValues(); assertNotNull(intents); assertThat(intents.size()).isEqualTo(3); final Intent intent1 = intents.get(0); - final String[] requiredPermission1 = requiredPermissions.get(0); assertThat(intent1.getPackage()).isEqualTo("android"); - assertThat(requiredPermission1).isEqualTo( - new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED}); final Intent intent2 = intents.get(1); - final String[] requiredPermission2 = requiredPermissions.get(1); assertThat(intent2.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME); - assertThat(requiredPermission2).isNull(); final Intent intent3 = intents.get(2); - final String[] requiredPermission3 = requiredPermissions.get(2); assertThat(intent3.getPackage()).isEqualTo("shared.package"); - assertThat(requiredPermission3).isNull(); } @Test diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java index 92c6db5b7b96..5240f581fd9f 100644 --- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java @@ -166,8 +166,7 @@ public class GestureLauncherServiceTest { new GestureLauncherService( mContext, mMetricsLogger, mQuickAccessWalletClient, mUiEventLogger); - withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + Settings.Secure.clearProviderForTest(); } private WalletLaunchedReceiver registerWalletLaunchedReceiver(String action) { @@ -223,7 +222,7 @@ public class GestureLauncherServiceTest { withDoubleTapPowerModeConfigValue( DOUBLE_TAP_POWER_DISABLED_MODE); withMultiTargetDoubleTapPowerGestureEnableSettingValue(false); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); @@ -244,7 +243,7 @@ public class GestureLauncherServiceTest { public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configFalseSettingEnabled() { withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE); withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); @@ -265,7 +264,7 @@ public class GestureLauncherServiceTest { public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configTrueSettingDisabled() { withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); withMultiTargetDoubleTapPowerGestureEnableSettingValue(false); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); @@ -286,7 +285,7 @@ public class GestureLauncherServiceTest { public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configTrueSettingEnabled() { withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); @@ -329,7 +328,31 @@ public class GestureLauncherServiceTest { public void testIsCameraDoubleTapPowerSettingEnabled_actionWallet() { withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); - withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); + withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); + + assertFalse( + mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_defaultActionCamera() { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); + withDefaultDoubleTapPowerGestureActionConfig(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + + assertTrue( + mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_defaultActionNotCamera() { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); + withDefaultDoubleTapPowerGestureActionConfig(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); assertFalse( mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( @@ -341,7 +364,7 @@ public class GestureLauncherServiceTest { public void testIsWalletDoubleTapPowerSettingEnabled() { withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); - withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); + withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); assertTrue( mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled( @@ -353,7 +376,7 @@ public class GestureLauncherServiceTest { public void testIsWalletDoubleTapPowerSettingEnabled_configDisabled() { withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE); withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); - withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); + withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); assertFalse( mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled( @@ -365,7 +388,7 @@ public class GestureLauncherServiceTest { public void testIsWalletDoubleTapPowerSettingEnabled_settingDisabled() { withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); withMultiTargetDoubleTapPowerGestureEnableSettingValue(false); - withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); + withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); assertFalse( mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled( @@ -377,7 +400,31 @@ public class GestureLauncherServiceTest { public void testIsWalletDoubleTapPowerSettingEnabled_actionCamera() { withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + + assertFalse( + mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsWalletDoubleTapPowerSettingEnabled_defaultActionWallet() { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); + withDefaultDoubleTapPowerGestureActionConfig(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); + + assertTrue( + mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsWalletDoubleTapPowerSettingEnabled_defaultActionNotWallet() { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); + withDefaultDoubleTapPowerGestureActionConfig(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); assertFalse( mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled( @@ -1858,7 +1905,7 @@ public class GestureLauncherServiceTest { UserHandle.USER_CURRENT); } - private void withDefaultDoubleTapPowerGestureAction(int action) { + private void withDoubleTapPowerGestureActionSettingValue(int action) { Settings.Secure.putIntForUser( mContentResolver, Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE, @@ -1866,6 +1913,12 @@ public class GestureLauncherServiceTest { UserHandle.USER_CURRENT); } + private void withDefaultDoubleTapPowerGestureActionConfig(int action) { + when(mResources.getInteger( + com.android.internal.R.integer.config_doubleTapPowerGestureMultiTargetDefaultAction + )).thenReturn(action); + } + private void withEmergencyGestureEnabledConfigValue(boolean enableConfigValue) { when(mResources.getBoolean( com.android.internal.R.bool.config_emergencyGestureEnabled)) @@ -1931,7 +1984,7 @@ public class GestureLauncherServiceTest { } private void enableWalletGesture() { - withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); + withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); @@ -1951,7 +2004,7 @@ public class GestureLauncherServiceTest { withDoubleTapPowerModeConfigValue( DOUBLE_TAP_POWER_MULTI_TARGET_MODE); withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); } else { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); 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 9528467f7ad1..39206dcf21ef 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -952,7 +952,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(99, si.getExtras().getInt("x")); } - public void testShortcutInfoSaveAndLoad() throws InterruptedException { + public void disabled_testShortcutInfoSaveAndLoad() throws InterruptedException { mRunningUsers.put(USER_11, true); setCaller(CALLING_PACKAGE_1, USER_11); @@ -1065,7 +1065,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { dumpUserFile(USER_11); } - public void testShortcutInfoSaveAndLoad_maskableBitmap() throws InterruptedException { + public void disabled_testShortcutInfoSaveAndLoad_maskableBitmap() throws InterruptedException { mRunningUsers.put(USER_11, true); setCaller(CALLING_PACKAGE_1, USER_11); @@ -1134,7 +1134,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { dumpUserFile(USER_11); } - public void testShortcutInfoSaveAndLoad_resId() throws InterruptedException { + public void disabled_testShortcutInfoSaveAndLoad_resId() throws InterruptedException { mRunningUsers.put(USER_11, true); setCaller(CALLING_PACKAGE_1, USER_11); @@ -1211,7 +1211,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(1, si.getRank()); } - public void testShortcutInfoSaveAndLoad_uri() throws InterruptedException { + public void disabled_testShortcutInfoSaveAndLoad_uri() throws InterruptedException { mRunningUsers.put(USER_11, true); setCaller(CALLING_PACKAGE_1, USER_11); @@ -1299,7 +1299,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals("uri_maskable", si.getIconUri()); } - public void testShortcutInfoSaveAndLoad_forBackup() { + public void disabled_testShortcutInfoSaveAndLoad_forBackup() { setCaller(CALLING_PACKAGE_1, USER_10); final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource( @@ -1368,7 +1368,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(0, si.getRank()); } - public void testShortcutInfoSaveAndLoad_forBackup_resId() { + public void disabled_testShortcutInfoSaveAndLoad_forBackup_resId() { setCaller(CALLING_PACKAGE_1, USER_10); final Icon res32x32 = Icon.createWithResource(mClientContext, R.drawable.black_32x32); @@ -1438,7 +1438,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(0, si.getRank()); } - public void testShortcutInfoSaveAndLoad_forBackup_uri() { + public void disabled_testShortcutInfoSaveAndLoad_forBackup_uri() { setCaller(CALLING_PACKAGE_1, USER_10); final Icon uriIcon = Icon.createWithContentUri("test_uri"); @@ -1547,7 +1547,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { }); } - public void testShortcutInfoSaveAndLoad_intents() { + public void disabled_testShortcutInfoSaveAndLoad_intents() { checkShortcutInfoSaveAndLoad_intents(new Intent(Intent.ACTION_VIEW)); mInjectedCurrentTimeMillis += INTERVAL; // reset throttling. @@ -1789,7 +1789,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertFalse(mManager.setDynamicShortcuts(list(si2))); } - public void testThrottling_localeChanges() { + public void disabled_testThrottling_localeChanges() { prepareCrossProfileDataSet(); dumpsysOnLogcat("Before save & load"); @@ -2078,7 +2078,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { } - public void testThrottling_resetByInternalCall() throws Exception { + public void disabled_testThrottling_resetByInternalCall() throws Exception { prepareCrossProfileDataSet(); dumpsysOnLogcat("Before save & load"); @@ -2173,7 +2173,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { }); } - public void testReportShortcutUsed() { + public void disabled_testReportShortcutUsed() { mRunningUsers.put(USER_11, true); runWithCaller(CALLING_PACKAGE_1, USER_11, () -> { @@ -2322,7 +2322,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { getTestContext().getPackageName())); } - public void testDumpCheckin() throws IOException { + public void disabled_testDumpCheckin() throws IOException { prepareCrossProfileDataSet(); // prepareCrossProfileDataSet() doesn't set any icons, so do set here. diff --git a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java index b76e0bc8cd14..ee8eb9b35088 100644 --- a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java @@ -42,10 +42,8 @@ import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.events.AuthenticationFailedInfo; import android.hardware.biometrics.events.AuthenticationSucceededInfo; import android.os.RemoteException; -import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; -import android.provider.Settings; import androidx.test.InstrumentationRegistry; import androidx.test.core.app.ApplicationProvider; @@ -153,8 +151,6 @@ public class AuthenticationPolicyServiceTest { when(mSecureLockDeviceService.disableSecureLockDevice(any())) .thenReturn(ERROR_UNSUPPORTED); } - - toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, false /* disable */); } @After @@ -256,24 +252,8 @@ public class AuthenticationPolicyServiceTest { } @Test - @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK}) - public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked_deviceLockEnabled() - throws RemoteException { - testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked( - true /* enabled */); - } - - @Test - @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK}) - public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked_deviceLockDisabled() + public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked() throws RemoteException { - toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, true /* disabled */); - testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked( - false /* enabled */); - } - - private void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked( - boolean enabled) throws RemoteException { // Device is currently not locked and Keyguard is not showing when(mKeyguardManager.isDeviceLocked(PRIMARY_USER_ID)).thenReturn(false); when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); @@ -284,11 +264,7 @@ public class AuthenticationPolicyServiceTest { } waitForAuthCompletion(); - if (enabled) { - verifyLockDevice(PRIMARY_USER_ID); - } else { - verifyNotLockDevice(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS, PRIMARY_USER_ID); - } + verifyLockDevice(PRIMARY_USER_ID); } @Test @@ -324,24 +300,8 @@ public class AuthenticationPolicyServiceTest { } @Test - @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK}) - public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser_deviceLockEnabled() + public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser() throws RemoteException { - testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser( - true /* enabled */); - } - - @Test - @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK}) - public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser_deviceLockDisabled() - throws RemoteException { - toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, true /* disabled */); - testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser( - false /* enabled */); - } - - private void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser( - boolean enabled) throws RemoteException { // Three failed primary auth attempts for (int i = 0; i < 3; i++) { mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID); @@ -353,11 +313,7 @@ public class AuthenticationPolicyServiceTest { } waitForAuthCompletion(); - if (enabled) { - verifyLockDevice(PRIMARY_USER_ID); - } else { - verifyNotLockDevice(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS, PRIMARY_USER_ID); - } + verifyLockDevice(PRIMARY_USER_ID); } @Test @@ -410,13 +366,10 @@ public class AuthenticationPolicyServiceTest { REASON_UNKNOWN, true, userId).build(); } + private AuthenticationFailedInfo authFailedInfo(int userId) { return new AuthenticationFailedInfo.Builder(BiometricSourceType.FINGERPRINT, REASON_UNKNOWN, userId).build(); } - private void toggleAdaptiveAuthSettingsOverride(int userId, boolean disable) { - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, disable ? 1 : 0, userId); - } } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 194d48a80a65..767c02bd268f 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -109,6 +109,7 @@ import com.android.internal.util.test.FakeSettingsProviderRule; import com.android.server.LocalServices; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.pm.BackgroundUserSoundNotifier; +import com.android.server.pm.UserManagerService; import com.android.server.vibrator.VibrationSession.Status; import org.junit.After; @@ -896,8 +897,8 @@ public class VibratorManagerServiceTest { } @Test - @EnableFlags(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS) public void vibrate_thenFgUserRequestsMute_getsCancelled() throws Throwable { + assumeTrue(UserManagerService.shouldShowNotificationForBackgroundUserSounds()); mockVibrators(1); VibratorManagerService service = createSystemReadyService(); @@ -2758,8 +2759,8 @@ public class VibratorManagerServiceTest { } @Test - @EnableFlags(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS) public void onExternalVibration_thenFgUserRequestsMute_doNotCancelVibration() throws Throwable { + assumeTrue(UserManagerService.shouldShowNotificationForBackgroundUserSounds()); mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); VibratorManagerService service = createSystemReadyService(); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java index d8f845389727..5e49c8cdb917 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java @@ -188,7 +188,7 @@ public class AppCompatFocusOverridesTest extends WindowTestsBase { } void checkShouldSendFakeFocusOnTopActivity(boolean expected) { - Assert.assertEquals(activity().top().mAppCompatController.getAppCompatFocusOverrides() + Assert.assertEquals(activity().top().mAppCompatController.getFocusOverrides() .shouldSendFakeFocus(), expected); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index dfd10ec86a20..d76a907ba010 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1583,42 +1583,6 @@ public class DisplayContentTests extends WindowTestsBase { is(Configuration.ORIENTATION_PORTRAIT)); } - @Test - public void testHybridRotationAnimation() { - final DisplayContent displayContent = mDefaultDisplay; - final WindowState statusBar = newWindowBuilder("statusBar", TYPE_STATUS_BAR).build(); - final WindowState navBar = newWindowBuilder("navBar", TYPE_NAVIGATION_BAR).build(); - final WindowState app = newWindowBuilder("app", TYPE_BASE_APPLICATION).build(); - final WindowState[] windows = { statusBar, navBar, app }; - makeWindowVisible(windows); - final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); - displayPolicy.addWindowLw(statusBar, statusBar.mAttrs); - displayPolicy.addWindowLw(navBar, navBar.mAttrs); - final ScreenRotationAnimation rotationAnim = new ScreenRotationAnimation(displayContent, - displayContent.getRotation()); - spyOn(rotationAnim); - // Assume that the display rotation is changed so it is frozen in preparation for animation. - doReturn(true).when(rotationAnim).hasScreenshot(); - displayContent.getDisplayRotation().setRotation((displayContent.getRotation() + 1) % 4); - displayContent.setRotationAnimation(rotationAnim); - // The fade rotation animation also starts to hide some non-app windows. - assertNotNull(displayContent.getAsyncRotationController()); - assertTrue(statusBar.isAnimating(PARENTS, ANIMATION_TYPE_TOKEN_TRANSFORM)); - - for (WindowState w : windows) { - w.setOrientationChanging(true); - } - // The display only waits for the app window to unfreeze. - assertFalse(displayContent.shouldSyncRotationChange(statusBar)); - assertFalse(displayContent.shouldSyncRotationChange(navBar)); - assertTrue(displayContent.shouldSyncRotationChange(app)); - // If all windows animated by fade rotation animation have done the orientation change, - // the animation controller should be cleared. - statusBar.setOrientationChanging(false); - navBar.setOrientationChanging(false); - assertNull(displayContent.getAsyncRotationController()); - } - @SetupWindows(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR, W_INPUT_METHOD, W_NOTIFICATION_SHADE }) @Test 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 7683c66c18f3..a84b374f9487 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -3892,11 +3892,12 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode(); // Draw letterbox. - mActivity.setVisible(false); - mActivity.mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN); - mActivity.mDisplayContent.mOpeningApps.add(mActivity); + mActivity.ensureActivityConfiguration(); addWindowToActivity(mActivity); mActivity.mRootWindowContainer.performSurfacePlacement(); + + // Verify mAppCompatDisplayInsets is created after recomputing. + assertTrue(mActivity.providesMaxBounds()); } private void assertLetterboxSurfacesDrawnBetweenActivityAndParentBounds(Rect parentBounds) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index ef58498b351c..546ecc6e38a5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -36,6 +36,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TAS import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS; import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; @@ -1911,6 +1912,53 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { OP_TYPE_REORDER_TO_TOP_OF_TASK); } + @Test + public void testApplyTransaction_setCanAffectSystemUiFlags() { + mController.unregisterOrganizer(mIOrganizer); + registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */); + + final Task task = createTask(mDisplayContent); + final TaskFragment tf = createTaskFragment(task); + + // Setting the flag to false. + TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(false).build(); + mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation); + + assertApplyTransactionAllowed(mTransaction); + + verify(tf).setCanAffectSystemUiFlags(false); + + // Setting the flag back to true. + operation = new TaskFragmentOperation.Builder( + OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(true).build(); + mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation); + + assertApplyTransactionAllowed(mTransaction); + + verify(tf).setCanAffectSystemUiFlags(true); + } + + @Test + public void testApplyTransaction_setCanAffectSystemUiFlags_failsIfNotSystemOrganizer() { + final Task task = createTask(mDisplayContent); + final TaskFragment tf = createTaskFragment(task); + + TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(false).build(); + mTransaction + .addTaskFragmentOperation(tf.getFragmentToken(), operation) + .setErrorCallbackToken(mErrorToken); + + assertApplyTransactionAllowed(mTransaction); + + // The pending event will be dispatched on the handler (from requestTraversal). + waitHandlerIdle(mWm.mAnimationHandler); + + assertTaskFragmentErrorTransaction(OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS, + SecurityException.class); + } + @NonNull private ActivityRecord setupUntrustedEmbeddingPipReparent() { final int pid = Binder.getCallingPid(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java index 1e0cef0514d8..1e16c97de647 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java @@ -113,7 +113,7 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase { mSnapshotPersistQueue = new SnapshotPersistQueue(); PersistInfoProvider provider = TaskSnapshotController.createPersistInfoProvider(mWm, userId -> FILES_DIR); - mPersister = new TaskSnapshotPersister(mSnapshotPersistQueue, provider); + mPersister = new TaskSnapshotPersister(mSnapshotPersistQueue, provider, false); mLoader = new AppSnapshotLoader(provider); mSnapshotPersistQueue.start(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 1febc9fb4742..38d3d2fec942 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -684,9 +684,7 @@ public class TaskTests extends WindowTestsBase { .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); Task task = rootTask.getBottomMostTask(); task.getRootActivity().setOrientation(SCREEN_ORIENTATION_UNSPECIFIED); - DisplayInfo info = new DisplayInfo(); - display.mDisplay.getDisplayInfo(info); - final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight); + final Rect fullScreenBounds = new Rect(display.getBounds()); final Rect freeformBounds = new Rect(fullScreenBounds); freeformBounds.inset((int) (freeformBounds.width() * 0.2), (int) (freeformBounds.height() * 0.2)); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index ab9abfc4a876..f6f473b4964d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -855,7 +855,6 @@ public class WindowStateTests extends WindowTestsBase { startingApp.updateResizingWindowIfNeeded(); assertTrue(mWm.mResizingWindows.contains(startingApp)); assertTrue(startingApp.isDrawn()); - assertFalse(startingApp.getOrientationChanging()); // Even if the display is frozen, invisible requested window should not be affected. mWm.startFreezingDisplay(0, 0, mDisplayContent); @@ -873,7 +872,6 @@ public class WindowStateTests extends WindowTestsBase { win.updateResizingWindowIfNeeded(); assertThat(mWm.mResizingWindows).contains(win); - assertTrue(win.getOrientationChanging()); mWm.mResizingWindows.remove(win); spyOn(win.mClient); @@ -892,7 +890,6 @@ public class WindowStateTests extends WindowTestsBase { // Even "resized" throws remote exception, it is still considered as reported. So the window // shouldn't be resized again (which may block unfreeze in real case). assertThat(mWm.mResizingWindows).doesNotContain(win); - assertFalse(win.getOrientationChanging()); } @Test diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java index 5db02e376f3d..7b1e4cc62dab 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -284,8 +285,11 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { // Stop the monitor mIpSecPacketLossDetector.close(); + mIpSecPacketLossDetector.close(); verifyStopped(); - verify(mIpSecTransform).close(); + + verify(mIpSecTransform, never()).close(); + verify(mContext).unregisterReceiver(any()); } @Test |