diff options
218 files changed, 3063 insertions, 1484 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 4cd6d6f03ee8..216bbab882a9 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -34264,6 +34264,7 @@ package android.os { method public boolean hasFileDescriptors(); method public boolean hasFileDescriptors(int, int); method public byte[] marshall(); + method @FlaggedApi("android.os.parcel_marshall_bytebuffer") public void marshall(@NonNull java.nio.ByteBuffer); method @NonNull public static android.os.Parcel obtain(); method @NonNull public static android.os.Parcel obtain(@NonNull android.os.IBinder); method @Deprecated @Nullable public Object[] readArray(@Nullable ClassLoader); @@ -34333,6 +34334,7 @@ package android.os { method public void setDataSize(int); method public void setPropagateAllowBlocking(); method public void unmarshall(@NonNull byte[], int, int); + method @FlaggedApi("android.os.parcel_marshall_bytebuffer") public void unmarshall(@NonNull java.nio.ByteBuffer); method public void writeArray(@Nullable Object[]); method public void writeBinderArray(@Nullable android.os.IBinder[]); method public void writeBinderList(@Nullable java.util.List<android.os.IBinder>); @@ -55423,6 +55425,7 @@ package android.view { method public void dispatchOnDraw(); method public void dispatchOnGlobalLayout(); method public boolean dispatchOnPreDraw(); + method @FlaggedApi("android.view.flags.enable_dispatch_on_scroll_changed") public void dispatchOnScrollChanged(); method public boolean isAlive(); method public void registerFrameCommitCallback(@NonNull Runnable); method @Deprecated public void removeGlobalOnLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 62816a2fa0f5..9cc7b8ff47c2 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1188,7 +1188,7 @@ public class ActivityManager { /** @hide Should this process state be considered jank perceptible? */ public static final boolean isProcStateJankPerceptible(int procState) { - if (Flags.jankPerceptibleNarrow()) { + if (Flags.jankPerceptibleNarrow() && !Flags.jankPerceptibleNarrowHoldback()) { return procState == PROCESS_STATE_PERSISTENT_UI || procState == PROCESS_STATE_TOP || procState == PROCESS_STATE_IMPORTANT_FOREGROUND diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 96b50960f0a2..f44c2305591d 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4014,7 +4014,7 @@ public final class ActivityThread extends ClientTransactionHandler return VM_PROCESS_STATE_JANK_PERCEPTIBLE; } - if (Flags.jankPerceptibleNarrow()) { + if (Flags.jankPerceptibleNarrow() && !Flags.jankPerceptibleNarrowHoldback()) { // Unlike other persistent processes, system server is often on // the critical path for application startup. Mark it explicitly // as jank perceptible regardless of processState. diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 00fa1c1a4f80..bdecbaeb455d 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -302,13 +302,23 @@ public class ApplicationPackageManager extends PackageManager { @Override public Intent getLaunchIntentForPackage(String packageName) { + return getLaunchIntentForPackage(packageName, false); + } + + @Override + @Nullable + public Intent getLaunchIntentForPackage(@NonNull String packageName, + boolean includeDirectBootUnaware) { + ResolveInfoFlags queryFlags = ResolveInfoFlags.of( + includeDirectBootUnaware ? MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE : 0); + // First see if the package has an INFO activity; the existence of // such an activity is implied to be the desired front-door for the // overall package (such as if it has multiple launcher entries). Intent intentToResolve = new Intent(Intent.ACTION_MAIN); intentToResolve.addCategory(Intent.CATEGORY_INFO); intentToResolve.setPackage(packageName); - List<ResolveInfo> ris = queryIntentActivities(intentToResolve, 0); + List<ResolveInfo> ris = queryIntentActivities(intentToResolve, queryFlags); // Otherwise, try to find a main launcher activity. if (ris == null || ris.size() <= 0) { @@ -316,7 +326,7 @@ public class ApplicationPackageManager extends PackageManager { intentToResolve.removeCategory(Intent.CATEGORY_INFO); intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER); intentToResolve.setPackage(packageName); - ris = queryIntentActivities(intentToResolve, 0); + ris = queryIntentActivities(intentToResolve, queryFlags); } if (ris == null || ris.size() <= 0) { return null; diff --git a/core/java/android/app/AutomaticZenRule.aidl b/core/java/android/app/AutomaticZenRule.aidl index feb21d657c6a..92f7d5235cf7 100644 --- a/core/java/android/app/AutomaticZenRule.aidl +++ b/core/java/android/app/AutomaticZenRule.aidl @@ -16,4 +16,6 @@ package android.app; -parcelable AutomaticZenRule;
\ No newline at end of file +parcelable AutomaticZenRule; + +parcelable AutomaticZenRule.AzrWithId;
\ No newline at end of file diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index fa977c93113a..1ce38ace75d8 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -228,7 +228,7 @@ public final class AutomaticZenRule implements Parcelable { public AutomaticZenRule(Parcel source) { enabled = source.readInt() == ENABLED; if (source.readInt() == ENABLED) { - name = getTrimmedString(source.readString()); + name = getTrimmedString(source.readString8()); } interruptionFilter = source.readInt(); conditionId = getTrimmedUri(source.readParcelable(null, android.net.Uri.class)); @@ -238,11 +238,11 @@ public final class AutomaticZenRule implements Parcelable { source.readParcelable(null, android.content.ComponentName.class)); creationTime = source.readLong(); mZenPolicy = source.readParcelable(null, ZenPolicy.class); - mPkg = source.readString(); + mPkg = source.readString8(); mDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class); mAllowManualInvocation = source.readBoolean(); mIconResId = source.readInt(); - mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH); + mTriggerDescription = getTrimmedString(source.readString8(), MAX_DESC_LENGTH); mType = source.readInt(); } @@ -514,7 +514,7 @@ public final class AutomaticZenRule implements Parcelable { dest.writeInt(enabled ? ENABLED : DISABLED); if (name != null) { dest.writeInt(1); - dest.writeString(name); + dest.writeString8(name); } else { dest.writeInt(0); } @@ -524,11 +524,11 @@ public final class AutomaticZenRule implements Parcelable { dest.writeParcelable(configurationActivity, 0); dest.writeLong(creationTime); dest.writeParcelable(mZenPolicy, 0); - dest.writeString(mPkg); + dest.writeString8(mPkg); dest.writeParcelable(mDeviceEffects, 0); dest.writeBoolean(mAllowManualInvocation); dest.writeInt(mIconResId); - dest.writeString(mTriggerDescription); + dest.writeString8(mTriggerDescription); dest.writeInt(mType); } @@ -843,4 +843,41 @@ public final class AutomaticZenRule implements Parcelable { return rule; } } + + /** @hide */ + public static final class AzrWithId implements Parcelable { + public final String mId; + public final AutomaticZenRule mRule; + + public AzrWithId(String id, AutomaticZenRule rule) { + mId = id; + mRule = rule; + } + + public static final Creator<AzrWithId> CREATOR = new Creator<>() { + @Override + public AzrWithId createFromParcel(Parcel in) { + return new AzrWithId( + in.readString8(), + in.readParcelable(AutomaticZenRule.class.getClassLoader(), + AutomaticZenRule.class)); + } + + @Override + public AzrWithId[] newArray(int size) { + return new AzrWithId[size]; + } + }; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mId); + dest.writeParcelable(mRule, flags); + } + + @Override + public int describeContents() { + return 0; + } + } } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 00df7246a300..1f0cd39a823c 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -224,7 +224,7 @@ interface INotificationManager void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted); ZenPolicy getDefaultZenPolicy(); AutomaticZenRule getAutomaticZenRule(String id); - Map<String, AutomaticZenRule> getAutomaticZenRules(); + ParceledListSlice getAutomaticZenRules(); String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg, boolean fromUser); boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule, boolean fromUser); boolean removeAutomaticZenRule(String id, boolean fromUser); diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 8af5b1bd40f8..19fecb9bf7c2 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -101,14 +101,20 @@ public class Instrumentation { */ public static final String REPORT_KEY_STREAMRESULT = "stream"; - static final String TAG = "Instrumentation"; + /** + * @hide + */ + public static final String TAG = "Instrumentation"; private static final long CONNECT_TIMEOUT_MILLIS = 60_000; private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); - // If set, will print the stack trace for activity starts within the process - static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE && + /** + * If set, will print the stack trace for activity starts within the process + * @hide + */ + public static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE && SystemProperties.getBoolean("persist.wm.debug.start_activity", false); static final boolean DEBUG_FINISH_ACTIVITY = Build.IS_DEBUGGABLE && SystemProperties.getBoolean("persist.wm.debug.finish_activity", false); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 050ef23b89e3..69e3ef9086d5 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1747,7 +1747,15 @@ public class NotificationManager { public Map<String, AutomaticZenRule> getAutomaticZenRules() { INotificationManager service = service(); try { - return service.getAutomaticZenRules(); + Map<String, AutomaticZenRule> result = new HashMap<>(); + ParceledListSlice<AutomaticZenRule.AzrWithId> parceledRules = + service.getAutomaticZenRules(); + if (parceledRules != null) { + for (AutomaticZenRule.AzrWithId rule : parceledRules.getList()) { + result.put(rule.mId, rule.mRule); + } + } + return result; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java index afe915eece26..dd87d288566e 100644 --- a/core/java/android/app/PictureInPictureParams.java +++ b/core/java/android/app/PictureInPictureParams.java @@ -594,7 +594,7 @@ public final class PictureInPictureParams implements Parcelable { * @see PictureInPictureParams.Builder#setSeamlessResizeEnabled(boolean) */ public boolean isSeamlessResizeEnabled() { - return mSeamlessResizeEnabled == null ? true : mSeamlessResizeEnabled; + return mSeamlessResizeEnabled == null ? false : mSeamlessResizeEnabled; } /** diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index 720e045dc944..e431426d5d09 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -163,3 +163,10 @@ flag { description: "Narrow the scope of Jank Perceptible" bug: "304837972" } + +flag { + name: "jank_perceptible_narrow_holdback" + namespace: "system_performance" + description: "Holdback study for jank_perceptible_narrow" + bug: "304837972" +} diff --git a/core/java/android/app/admin/StringSetIntersection.java b/core/java/android/app/admin/StringSetIntersection.java new file mode 100644 index 000000000000..5f2031e93ad9 --- /dev/null +++ b/core/java/android/app/admin/StringSetIntersection.java @@ -0,0 +1,80 @@ +/* + * 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 android.app.admin; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Set; + +/** + * Class to identify a intersection resolution mechanism for {@code Set<String>} policies, it's + * used to resolve the enforced policy when being set by multiple admins (see {@link + * PolicyState#getResolutionMechanism()}). + * + * @hide + */ +public final class StringSetIntersection extends ResolutionMechanism<Set<String>> { + + /** + * Intersection resolution for policies represented {@code Set<String>} which resolves as the + * intersection of all sets. + */ + @NonNull + public static final StringSetIntersection STRING_SET_INTERSECTION = new StringSetIntersection(); + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + return o != null && getClass() == o.getClass(); + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public String toString() { + return "StringSetIntersection {}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) {} + + @NonNull + public static final Parcelable.Creator<StringSetIntersection> CREATOR = + new Parcelable.Creator<StringSetIntersection>() { + @Override + public StringSetIntersection createFromParcel(Parcel source) { + return new StringSetIntersection(); + } + + @Override + public StringSetIntersection[] newArray(int size) { + return new StringSetIntersection[size]; + } + }; +} diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 572bffe6c6a4..b87ef7061c39 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -412,3 +412,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "use_policy_intersection_for_permitted_input_methods" + namespace: "enterprise" + description: "When deciding on permitted input methods, use policy intersection instead of last recorded policy." + bug: "340914586" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java index a13af7f1ddcd..d7d6262599da 100644 --- a/core/java/android/app/wallpaper/WallpaperDescription.java +++ b/core/java/android/app/wallpaper/WallpaperDescription.java @@ -168,6 +168,12 @@ public final class WallpaperDescription implements Parcelable { return mSampleSize; } + @Override + public String toString() { + String component = (mComponent != null) ? mComponent.toString() : "{null}"; + return component + ":" + mId; + } + ////// Comparison overrides @Override diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 49fd6344270e..53966b8af533 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -5964,7 +5964,39 @@ public abstract class PackageManager { * * @see #getLaunchIntentSenderForPackage(String) */ - public abstract @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName); + public abstract @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName); + + /** + * Returns a "good" intent to launch a front-door activity in a package. + * This is used, for example, to implement an "open" button when browsing + * through packages. The current implementation looks first for a main + * activity in the category {@link Intent#CATEGORY_INFO}, and next for a + * main activity in the category {@link Intent#CATEGORY_LAUNCHER}. Returns + * <code>null</code> if neither are found. + * + * <p>Consider using {@link #getLaunchIntentSenderForPackage(String)} if + * the caller is not allowed to query for the <code>packageName</code>. + * + * @param packageName The name of the package to inspect. + * @param includeDirectBootUnaware When {@code true}, activities that are direct-boot-unaware + * will be considered even if the device hasn't been unlocked (i.e. querying will be done + * with {@code MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE}). + * + * @return A fully-qualified {@link Intent} that can be used to launch the + * main activity in the package. Returns <code>null</code> if the package + * does not contain such an activity, or if <em>packageName</em> is not + * recognized. + * + * @see #getLaunchIntentSenderForPackage(String) + * + * @hide + */ + public @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName, + boolean includeDirectBootUnaware) { + throw new UnsupportedOperationException( + "getLaunchIntentForPackage(packageName, includeDirectBootUnaware) not implemented" + + " in subclass"); + } /** * Return a "good" intent to launch a front-door Leanback activity in a diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java index 4ed0fc056e7d..c3c8c3d63bec 100644 --- a/core/java/android/hardware/display/DisplayTopology.java +++ b/core/java/android/hardware/display/DisplayTopology.java @@ -600,8 +600,7 @@ public final class DisplayTopology implements Parcelable { private void addDisplay(int displayId, float width, float height, boolean shouldLog) { if (findDisplay(displayId, mRoot) != null) { - throw new IllegalArgumentException( - "DisplayTopology: attempting to add a display that already exists"); + return; } if (mRoot == null) { mRoot = new TreeNode(displayId, width, height, POSITION_LEFT, /* offset= */ 0); diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 6cb49b3ea166..4a9928532b93 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -27,6 +28,7 @@ import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.AppOpsManager; import android.compat.annotation.UnsupportedAppUsage; +import android.os.Flags; import android.ravenwood.annotation.RavenwoodClassLoadHook; import android.ravenwood.annotation.RavenwoodKeepWholeClass; import android.ravenwood.annotation.RavenwoodReplace; @@ -837,9 +839,8 @@ public final class Parcel { * @param buffer The ByteBuffer to write the data to. * @throws ReadOnlyBufferException if the buffer is read-only. * @throws BufferOverflowException if the buffer is too small. - * - * @hide */ + @FlaggedApi(Flags.FLAG_PARCEL_MARSHALL_BYTEBUFFER) public final void marshall(@NonNull ByteBuffer buffer) { if (buffer == null) { throw new NullPointerException(); @@ -875,9 +876,8 @@ public final class Parcel { * Fills the raw bytes of this Parcel with data from the supplied buffer. * * @param buffer will read buffer.remaining() bytes from the buffer. - * - * @hide */ + @FlaggedApi(Flags.FLAG_PARCEL_MARSHALL_BYTEBUFFER) public final void unmarshall(@NonNull ByteBuffer buffer) { if (buffer == null) { throw new NullPointerException(); diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 86acb2b21cfa..0150d171d51c 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -354,6 +354,15 @@ flag { flag { namespace: "system_performance" + name: "parcel_marshall_bytebuffer" + is_exported: true + description: "Parcel marshal/unmarshall APIs that use ByteBuffer." + is_fixed_read_only: true + bug: "401362825" +} + +flag { + namespace: "system_performance" name: "perfetto_sdk_tracing" description: "Tracing using Perfetto SDK." bug: "303199244" diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 4cbd5beb3a8c..fce2df185461 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -62,6 +62,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.res.Resources; import android.net.Uri; import android.os.Build; @@ -460,14 +461,21 @@ public class ZenModeConfig implements Parcelable { } private static void readRulesFromParcel(ArrayMap<String, ZenRule> ruleMap, Parcel source) { - final int len = source.readInt(); + int len = source.readInt(); if (len > 0) { final String[] ids = new String[len]; - final ZenRule[] rules = new ZenRule[len]; - source.readStringArray(ids); - source.readTypedArray(rules, ZenRule.CREATOR); + source.readString8Array(ids); + ParceledListSlice<?> parceledRules = source.readParcelable( + ZenRule.class.getClassLoader(), ParceledListSlice.class); + List<?> rules = parceledRules != null ? parceledRules.getList() : new ArrayList<>(); + if (rules.size() != len) { + Slog.wtf(TAG, String.format( + "Unexpected parceled rules count (%s != %s), throwing them out", + rules.size(), len)); + len = 0; + } for (int i = 0; i < len; i++) { - ruleMap.put(ids[i], rules[i]); + ruleMap.put(ids[i], (ZenRule) rules.get(i)); } } } @@ -485,8 +493,8 @@ public class ZenModeConfig implements Parcelable { } dest.writeInt(user); dest.writeParcelable(manualRule, 0); - writeRulesToParcel(automaticRules, dest); - writeRulesToParcel(deletedRules, dest); + writeRulesToParcel(automaticRules, dest, flags); + writeRulesToParcel(deletedRules, dest, flags); if (!Flags.modesUi()) { dest.writeInt(allowAlarms ? 1 : 0); dest.writeInt(allowMedia ? 1 : 0); @@ -501,18 +509,19 @@ public class ZenModeConfig implements Parcelable { } } - private static void writeRulesToParcel(ArrayMap<String, ZenRule> ruleMap, Parcel dest) { + private static void writeRulesToParcel(ArrayMap<String, ZenRule> ruleMap, Parcel dest, + int flags) { if (!ruleMap.isEmpty()) { final int len = ruleMap.size(); final String[] ids = new String[len]; - final ZenRule[] rules = new ZenRule[len]; + final ArrayList<ZenRule> rules = new ArrayList<>(); for (int i = 0; i < len; i++) { ids[i] = ruleMap.keyAt(i); - rules[i] = ruleMap.valueAt(i); + rules.add(ruleMap.valueAt(i)); } dest.writeInt(len); - dest.writeStringArray(ids); - dest.writeTypedArray(rules, 0); + dest.writeString8Array(ids); + dest.writeParcelable(new ParceledListSlice<>(rules), flags); } else { dest.writeInt(0); } @@ -2636,7 +2645,7 @@ public class ZenModeConfig implements Parcelable { enabled = source.readInt() == 1; snoozing = source.readInt() == 1; if (source.readInt() == 1) { - name = source.readString(); + name = source.readString8(); } zenMode = source.readInt(); conditionId = source.readParcelable(null, android.net.Uri.class); @@ -2644,18 +2653,18 @@ public class ZenModeConfig implements Parcelable { component = source.readParcelable(null, android.content.ComponentName.class); configurationActivity = source.readParcelable(null, android.content.ComponentName.class); if (source.readInt() == 1) { - id = source.readString(); + id = source.readString8(); } creationTime = source.readLong(); if (source.readInt() == 1) { - enabler = source.readString(); + enabler = source.readString8(); } zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class); zenDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class); - pkg = source.readString(); + pkg = source.readString8(); allowManualInvocation = source.readBoolean(); - iconResName = source.readString(); - triggerDescription = source.readString(); + iconResName = source.readString8(); + triggerDescription = source.readString8(); type = source.readInt(); userModifiedFields = source.readInt(); zenPolicyUserModifiedFields = source.readInt(); @@ -2703,7 +2712,7 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(snoozing ? 1 : 0); if (name != null) { dest.writeInt(1); - dest.writeString(name); + dest.writeString8(name); } else { dest.writeInt(0); } @@ -2714,23 +2723,23 @@ public class ZenModeConfig implements Parcelable { dest.writeParcelable(configurationActivity, 0); if (id != null) { dest.writeInt(1); - dest.writeString(id); + dest.writeString8(id); } else { dest.writeInt(0); } dest.writeLong(creationTime); if (enabler != null) { dest.writeInt(1); - dest.writeString(enabler); + dest.writeString8(enabler); } else { dest.writeInt(0); } dest.writeParcelable(zenPolicy, 0); dest.writeParcelable(zenDeviceEffects, 0); - dest.writeString(pkg); + dest.writeString8(pkg); dest.writeBoolean(allowManualInvocation); - dest.writeString(iconResName); - dest.writeString(triggerDescription); + dest.writeString8(iconResName); + dest.writeString8(triggerDescription); dest.writeInt(type); dest.writeInt(userModifiedFields); dest.writeInt(zenPolicyUserModifiedFields); diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index 3b444c44c368..fc66e49ff6b0 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -16,6 +16,9 @@ package android.view; +import static android.view.flags.Flags.FLAG_ENABLE_DISPATCH_ON_SCROLL_CHANGED; + +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -1258,8 +1261,9 @@ public final class ViewTreeObserver { /** * Notifies registered listeners that something has scrolled. */ + @FlaggedApi(FLAG_ENABLE_DISPATCH_ON_SCROLL_CHANGED) @UnsupportedAppUsage - final void dispatchOnScrollChanged() { + public final void dispatchOnScrollChanged() { // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents diff --git a/core/java/android/view/flags/view_tree_observer_flags.aconfig b/core/java/android/view/flags/view_tree_observer_flags.aconfig new file mode 100644 index 000000000000..82f3300a87cb --- /dev/null +++ b/core/java/android/view/flags/view_tree_observer_flags.aconfig @@ -0,0 +1,9 @@ +package: "android.view.flags" +container: "system" + +flag { + name: "enable_dispatch_on_scroll_changed" + namespace: "toolkit" + description: "Feature flag for enabling the dispatchOnScrollChanged method in ViewTreeObserver." + bug: "238109286" +}
\ No newline at end of file diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 2ed9c3a48add..8f7f94196eb1 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -16,6 +16,7 @@ package android.window; +import static android.app.Instrumentation.DEBUG_START_ACTIVITY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; @@ -32,6 +33,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.TestApi; +import android.app.Instrumentation; import android.app.PendingIntent; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; @@ -45,6 +47,7 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; +import android.util.Log; import android.view.InsetsFrameProvider; import android.view.InsetsSource; import android.view.SurfaceControl; @@ -642,6 +645,10 @@ public final class WindowContainerTransaction implements Parcelable { */ @NonNull public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) { + if (DEBUG_START_ACTIVITY) { + Log.d(Instrumentation.TAG, "WCT.startTask: taskId=" + taskId + + " options=" + options, new Throwable()); + } mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options)); return this; } @@ -655,11 +662,15 @@ public final class WindowContainerTransaction implements Parcelable { */ @NonNull public WindowContainerTransaction sendPendingIntent(@Nullable PendingIntent sender, - @Nullable Intent intent, @Nullable Bundle options) { + @Nullable Intent fillInIntent, @Nullable Bundle options) { + if (DEBUG_START_ACTIVITY) { + Log.d(Instrumentation.TAG, "WCT.sendPendingIntent: sender=" + sender.getIntent() + + " fillInIntent=" + fillInIntent + " options=" + options, new Throwable()); + } mHierarchyOps.add(new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT) .setLaunchOptions(options) .setPendingIntent(sender) - .setActivityIntent(intent) + .setActivityIntent(fillInIntent) .build()); return this; } @@ -674,6 +685,10 @@ public final class WindowContainerTransaction implements Parcelable { @NonNull public WindowContainerTransaction startShortcut(@NonNull String callingPackage, @NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) { + if (DEBUG_START_ACTIVITY) { + Log.d(Instrumentation.TAG, "WCT.startShortcut: shortcutInfo=" + shortcutInfo + + " options=" + options, new Throwable()); + } mHierarchyOps.add(HierarchyOp.createForStartShortcut( callingPackage, shortcutInfo, options)); return this; diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 816270235446..8dd0457248a4 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -287,6 +287,17 @@ flag { } flag { + name: "use_visible_requested_for_process_tracker" + namespace: "windowing_frontend" + description: "Do not count closing activity as visible process" + bug: "396653764" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "ensure_wallpaper_in_transitions" namespace: "windowing_frontend" description: "Ensure that wallpaper window tokens are always present/available for collection in transitions" diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java index 3472d681a486..f6e2a4df8cca 100644 --- a/core/java/com/android/internal/widget/NotificationProgressBar.java +++ b/core/java/com/android/internal/widget/NotificationProgressBar.java @@ -78,6 +78,13 @@ public final class NotificationProgressBar extends ProgressBar implements @Nullable private List<DrawablePart> mProgressDrawableParts = null; + /** @see R.styleable#NotificationProgressBar_segMinWidth */ + private final float mSegMinWidth; + /** @see R.styleable#NotificationProgressBar_segSegGap */ + private final float mSegSegGap; + /** @see R.styleable#NotificationProgressBar_segPointGap */ + private final float mSegPointGap; + @Nullable private Drawable mTracker = null; private boolean mHasTrackerIcon = false; @@ -128,6 +135,10 @@ public final class NotificationProgressBar extends ProgressBar implements Log.e(TAG, "Can't get NotificationProgressDrawable", ex); } + mSegMinWidth = a.getDimension(R.styleable.NotificationProgressBar_segMinWidth, 0f); + mSegSegGap = a.getDimension(R.styleable.NotificationProgressBar_segSegGap, 0f); + mSegPointGap = a.getDimension(R.styleable.NotificationProgressBar_segPointGap, 0f); + // Supports setting the tracker in xml, but ProgressStyle notifications set/override it // via {@code #setProgressTrackerIcon}. final Drawable tracker = a.getDrawable(R.styleable.NotificationProgressBar_tracker); @@ -444,30 +455,26 @@ public final class NotificationProgressBar extends ProgressBar implements return; } - final float segSegGap = mNotificationProgressDrawable.getSegSegGap(); - final float segPointGap = mNotificationProgressDrawable.getSegPointGap(); final float pointRadius = mNotificationProgressDrawable.getPointRadius(); mProgressDrawableParts = processPartsAndConvertToDrawableParts( mParts, width, - segSegGap, - segPointGap, + mSegSegGap, + mSegPointGap, pointRadius, mHasTrackerIcon, mTrackerDrawWidth ); - final float segmentMinWidth = mNotificationProgressDrawable.getSegmentMinWidth(); final float progressFraction = getProgressFraction(); final boolean isStyledByProgress = mProgressModel.isStyledByProgress(); - final float progressGap = - mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap(); + final float progressGap = mHasTrackerIcon ? 0F : mSegSegGap; Pair<List<DrawablePart>, Float> p = null; try { p = maybeStretchAndRescaleSegments( mParts, mProgressDrawableParts, - segmentMinWidth, + mSegMinWidth, pointRadius, progressFraction, isStyledByProgress, @@ -492,11 +499,11 @@ public final class NotificationProgressBar extends ProgressBar implements mProgressModel.getProgress(), getMax(), width, - segSegGap, - segPointGap, + mSegSegGap, + mSegPointGap, pointRadius, mHasTrackerIcon, - segmentMinWidth, + mSegMinWidth, isStyledByProgress, mTrackerDrawWidth); } catch (NotEnoughWidthToFitAllPartsException ex) { @@ -521,11 +528,11 @@ public final class NotificationProgressBar extends ProgressBar implements mProgressModel.getProgress(), getMax(), width, - segSegGap, - segPointGap, + mSegSegGap, + mSegPointGap, pointRadius, mHasTrackerIcon, - segmentMinWidth, + mSegMinWidth, isStyledByProgress, mTrackerDrawWidth); } catch (NotEnoughWidthToFitAllPartsException ex) { diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java index b1096107f04b..32b283af29c5 100644 --- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java +++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java @@ -84,27 +84,6 @@ public final class NotificationProgressDrawable extends Drawable { } /** - * Returns the gap between two segments. - */ - public float getSegSegGap() { - return mState.mSegSegGap; - } - - /** - * Returns the gap between a segment and a point. - */ - public float getSegPointGap() { - return mState.mSegPointGap; - } - - /** - * Returns the gap between a segment and a point. - */ - public float getSegmentMinWidth() { - return mState.mSegmentMinWidth; - } - - /** * Returns the radius for the points. */ public float getPointRadius() { @@ -241,11 +220,6 @@ public final class NotificationProgressDrawable extends Drawable { mState.setDensity(resolveDensity(r, 0)); - final TypedArray a = obtainAttributes(r, theme, attrs, - R.styleable.NotificationProgressDrawable); - updateStateFromTypedArray(a); - a.recycle(); - inflateChildElements(r, parser, attrs, theme); updateLocalState(); @@ -262,13 +236,6 @@ public final class NotificationProgressDrawable extends Drawable { state.setDensity(resolveDensity(t.getResources(), 0)); - if (state.mThemeAttrs != null) { - final TypedArray a = t.resolveAttributes( - state.mThemeAttrs, R.styleable.NotificationProgressDrawable); - updateStateFromTypedArray(a); - a.recycle(); - } - applyThemeChildElements(t); updateLocalState(); @@ -279,21 +246,6 @@ public final class NotificationProgressDrawable extends Drawable { return (mState.canApplyTheme()) || super.canApplyTheme(); } - private void updateStateFromTypedArray(TypedArray a) { - final State state = mState; - - // Account for any configuration changes. - state.mChangingConfigurations |= a.getChangingConfigurations(); - - // Extract the theme attributes, if any. - state.mThemeAttrs = a.extractThemeAttrs(); - - state.mSegSegGap = a.getDimension(R.styleable.NotificationProgressDrawable_segSegGap, - state.mSegSegGap); - state.mSegPointGap = a.getDimension(R.styleable.NotificationProgressDrawable_segPointGap, - state.mSegPointGap); - } - private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { TypedArray a; @@ -357,8 +309,6 @@ public final class NotificationProgressDrawable extends Drawable { // Extract the theme attributes, if any. state.mThemeAttrsSegments = a.extractThemeAttrs(); - state.mSegmentMinWidth = a.getDimension( - R.styleable.NotificationProgressDrawableSegments_minWidth, state.mSegmentMinWidth); state.mSegmentHeight = a.getDimension( R.styleable.NotificationProgressDrawableSegments_height, state.mSegmentHeight); state.mFadedSegmentHeight = a.getDimension( @@ -588,9 +538,6 @@ public final class NotificationProgressDrawable extends Drawable { static final class State extends ConstantState { @Config int mChangingConfigurations; - float mSegSegGap = 0.0f; - float mSegPointGap = 0.0f; - float mSegmentMinWidth = 0.0f; float mSegmentHeight; float mFadedSegmentHeight; float mSegmentCornerRadius; @@ -610,9 +557,6 @@ public final class NotificationProgressDrawable extends Drawable { State(@NonNull State orig, @Nullable Resources res) { mChangingConfigurations = orig.mChangingConfigurations; - mSegSegGap = orig.mSegSegGap; - mSegPointGap = orig.mSegPointGap; - mSegmentMinWidth = orig.mSegmentMinWidth; mSegmentHeight = orig.mSegmentHeight; mFadedSegmentHeight = orig.mFadedSegmentHeight; mSegmentCornerRadius = orig.mSegmentCornerRadius; @@ -631,18 +575,6 @@ public final class NotificationProgressDrawable extends Drawable { } private void applyDensityScaling(int sourceDensity, int targetDensity) { - if (mSegSegGap > 0) { - mSegSegGap = scaleFromDensity( - mSegSegGap, sourceDensity, targetDensity); - } - if (mSegPointGap > 0) { - mSegPointGap = scaleFromDensity( - mSegPointGap, sourceDensity, targetDensity); - } - if (mSegmentMinWidth > 0) { - mSegmentMinWidth = scaleFromDensity( - mSegmentMinWidth, sourceDensity, targetDensity); - } if (mSegmentHeight > 0) { mSegmentHeight = scaleFromDensity( mSegmentHeight, sourceDensity, targetDensity); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e16ce9849ff2..9e0200481421 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5394,13 +5394,13 @@ corresponding permission such as {@link #HEAD_TRACKING} or {@link #FACE_TRACKING} for the data being accessed. - <p>Protection level: normal|appop + <p>Protection level: signature|privileged @SystemApi @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) @hide --> <permission android:name="android.permission.XR_TRACKING_IN_BACKGROUND" - android:protectionLevel="normal|appop" + android:protectionLevel="signature|privileged" android:description="@string/permdesc_xr_tracking_in_background" android:label="@string/permlab_xr_tracking_in_background" android:featureFlag="android.xr.xr_manifest_entries" /> diff --git a/core/res/res/drawable/notification_progress.xml b/core/res/res/drawable/notification_progress.xml index ff5450ee106f..92a0a6a4e21b 100644 --- a/core/res/res/drawable/notification_progress.xml +++ b/core/res/res/drawable/notification_progress.xml @@ -19,12 +19,9 @@ android:gravity="center_vertical|fill_horizontal"> <com.android.internal.widget.NotificationProgressDrawable android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:segSegGap="@dimen/notification_progress_segSeg_gap" - android:segPointGap="@dimen/notification_progress_segPoint_gap"> + android:layout_height="wrap_content"> <segments android:color="?attr/colorProgressBackgroundNormal" - android:minWidth="@dimen/notification_progress_segments_min_width" android:height="@dimen/notification_progress_segments_height" android:fadedHeight="@dimen/notification_progress_segments_faded_height" android:cornerRadius="@dimen/notification_progress_segments_corner_radius"/> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index d2c993aecb0d..647e3dc2d268 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5573,6 +5573,14 @@ <!-- @hide internal use only --> <declare-styleable name="NotificationProgressBar"> + <!-- Minimum required drawing width for segments. The drawing width refers to the width + after the original segments have been adjusted for the neighboring Points and gaps. + This is enforced by stretching the segments that are too short. --> + <attr name="segMinWidth" format="dimension" /> + <!-- The gap between two segments. --> + <attr name="segSegGap" format="dimension" /> + <!-- The gap between a segment and a point. --> + <attr name="segPointGap" format="dimension" /> <!-- Draws the tracker on a NotificationProgressBar. --> <attr name="tracker" format="reference" /> <!-- Height of the tracker. --> @@ -7580,25 +7588,9 @@ <!-- NotificationProgressDrawable class --> <!-- ================================== --> - <!-- Drawable used to render a notification progress bar, with segments and points. --> - <!-- @hide internal use only --> - <declare-styleable name="NotificationProgressDrawable"> - <!-- The gap between two segments. --> - <attr name="segSegGap" format="dimension" /> - <!-- The gap between a segment and a point. --> - <attr name="segPointGap" format="dimension" /> - </declare-styleable> - <!-- Used to config the segments of a NotificationProgressDrawable. --> <!-- @hide internal use only --> <declare-styleable name="NotificationProgressDrawableSegments"> - <!-- TODO: b/390196782 - maybe move this to NotificationProgressBar, because that's the only - place this is used actually. Same for NotificationProgressDrawable.segSegGap/segPointGap - above. --> - <!-- Minimum required drawing width. The drawing width refers to the width after - the original segments have been adjusted for the neighboring Points and gaps. This is - enforced by stretching the segments that are too short. --> - <attr name="minWidth" /> <!-- Height of the solid segments. --> <attr name="height" /> <!-- Height of the faded segments. --> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 73681d26f297..8f13ee1ccb49 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -503,6 +503,9 @@ please see styles_device_defaults.xml. <style name="Widget.Material.Notification.ProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal" /> <style name="Widget.Material.Notification.NotificationProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal"> + <item name="segMinWidth">@dimen/notification_progress_segments_min_width</item> + <item name="segSegGap">@dimen/notification_progress_segSeg_gap</item> + <item name="segPointGap">@dimen/notification_progress_segPoint_gap</item> <item name="progressDrawable">@drawable/notification_progress</item> <item name="trackerHeight">@dimen/notification_progress_tracker_height</item> </style> diff --git a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java index 62d89f6dc846..146b386e5a4b 100644 --- a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java +++ b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java @@ -16,19 +16,37 @@ package android.app; +import static android.content.Intent.ACTION_MAIN; +import static android.content.Intent.CATEGORY_INFO; +import static android.content.Intent.CATEGORY_LAUNCHER; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.os.storage.VolumeInfo.STATE_MOUNTED; import static android.os.storage.VolumeInfo.STATE_UNMOUNTED; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager.ResolveInfoFlags; +import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -44,6 +62,7 @@ import com.android.internal.annotations.VisibleForTesting; import junit.framework.TestCase; +import org.mockito.ArgumentMatcher; import org.mockito.Mockito; import org.xmlpull.v1.XmlPullParser; @@ -102,14 +121,14 @@ public class ApplicationPackageManagerTest extends TestCase { sVolumes.add(sPrivateUnmountedVol); } - private static final class MockedApplicationPackageManager extends ApplicationPackageManager { + public static class MockedApplicationPackageManager extends ApplicationPackageManager { private boolean mForceAllowOnExternal = false; private boolean mAllow3rdPartyOnInternal = true; private HashMap<ApplicationInfo, Resources> mResourcesMap; public MockedApplicationPackageManager() { super(null, null); - mResourcesMap = new HashMap<ApplicationInfo, Resources>(); + mResourcesMap = new HashMap<>(); } public void setForceAllowOnExternal(boolean forceAllowOnExternal) { @@ -153,7 +172,7 @@ public class ApplicationPackageManagerTest extends TestCase { } private StorageManager getMockedStorageManager() { - StorageManager storageManager = Mockito.mock(StorageManager.class); + StorageManager storageManager = mock(StorageManager.class); Mockito.when(storageManager.getVolumes()).thenReturn(sVolumes); Mockito.when(storageManager.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL)) .thenReturn(sInternalVol); @@ -190,7 +209,7 @@ public class ApplicationPackageManagerTest extends TestCase { sysAppInfo.flags = ApplicationInfo.FLAG_SYSTEM; StorageManager storageManager = getMockedStorageManager(); - IPackageManager pm = Mockito.mock(IPackageManager.class); + IPackageManager pm = mock(IPackageManager.class); MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); @@ -220,7 +239,7 @@ public class ApplicationPackageManagerTest extends TestCase { ApplicationInfo appInfo = new ApplicationInfo(); StorageManager storageManager = getMockedStorageManager(); - IPackageManager pm = Mockito.mock(IPackageManager.class); + IPackageManager pm = mock(IPackageManager.class); Mockito.when(pm.isPackageDeviceAdminOnAnyUser(Mockito.anyString())).thenReturn(false); MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); @@ -249,7 +268,7 @@ public class ApplicationPackageManagerTest extends TestCase { ApplicationInfo appInfo = new ApplicationInfo(); StorageManager storageManager = getMockedStorageManager(); - IPackageManager pm = Mockito.mock(IPackageManager.class); + IPackageManager pm = mock(IPackageManager.class); MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); appPkgMgr.setForceAllowOnExternal(true); @@ -291,15 +310,15 @@ public class ApplicationPackageManagerTest extends TestCase { public void testExtractPackageItemInfoAttributes_noMetaData() { final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); - final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class); + final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class); assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, null, new int[]{})).isNull(); } public void testExtractPackageItemInfoAttributes_noParser() { final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); - final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class); - final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class); + final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class); + final ApplicationInfo applicationInfo = mock(ApplicationInfo.class); when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo); assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, null, new int[]{})).isNull(); @@ -307,8 +326,8 @@ public class ApplicationPackageManagerTest extends TestCase { public void testExtractPackageItemInfoAttributes_noMetaDataXml() { final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); - final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class); - final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class); + final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class); + final ApplicationInfo applicationInfo = mock(ApplicationInfo.class); when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo); when(packageItemInfo.loadXmlMetaData(any(), any())).thenReturn(null); assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, null, @@ -318,9 +337,9 @@ public class ApplicationPackageManagerTest extends TestCase { public void testExtractPackageItemInfoAttributes_nonMatchingRootTag() throws Exception { final String rootTag = "rootTag"; final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); - final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class); - final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class); - final XmlResourceParser parser = Mockito.mock(XmlResourceParser.class); + final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class); + final ApplicationInfo applicationInfo = mock(ApplicationInfo.class); + final XmlResourceParser parser = mock(XmlResourceParser.class); when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo); packageItemInfo.metaData = new Bundle(); @@ -334,11 +353,11 @@ public class ApplicationPackageManagerTest extends TestCase { public void testExtractPackageItemInfoAttributes_successfulExtraction() throws Exception { final String rootTag = "rootTag"; final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); - final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class); - final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class); - final XmlResourceParser parser = Mockito.mock(XmlResourceParser.class); - final Resources resources = Mockito.mock(Resources.class); - final TypedArray attributes = Mockito.mock(TypedArray.class); + final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class); + final ApplicationInfo applicationInfo = mock(ApplicationInfo.class); + final XmlResourceParser parser = mock(XmlResourceParser.class); + final Resources resources = mock(Resources.class); + final TypedArray attributes = mock(TypedArray.class); when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo); packageItemInfo.metaData = new Bundle(); @@ -351,4 +370,123 @@ public class ApplicationPackageManagerTest extends TestCase { assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, rootTag, new int[]{})).isEqualTo(attributes); } + + public void testGetLaunchIntentForPackage_categoryInfoActivity_returnsIt() throws Exception { + String pkg = "com.some.package"; + int userId = 42; + ResolveInfo categoryInfoResolveInfo = new ResolveInfo(); + categoryInfoResolveInfo.activityInfo = new ActivityInfo(); + categoryInfoResolveInfo.activityInfo.packageName = pkg; + categoryInfoResolveInfo.activityInfo.name = "activity"; + Intent baseIntent = new Intent(ACTION_MAIN).setPackage(pkg); + + final MockedApplicationPackageManager pm = spy(new MockedApplicationPackageManager()); + doReturn(userId).when(pm).getUserId(); + doReturn(List.of(categoryInfoResolveInfo)) + .when(pm).queryIntentActivitiesAsUser( + eqIntent(new Intent(baseIntent).addCategory(CATEGORY_INFO)), + any(ResolveInfoFlags.class), + anyInt()); + doReturn( + List.of()) + .when(pm).queryIntentActivitiesAsUser( + eqIntent(new Intent(baseIntent).addCategory(CATEGORY_LAUNCHER)), + any(ResolveInfoFlags.class), + anyInt()); + + Intent intent = pm.getLaunchIntentForPackage(pkg, true); + + assertThat(intent).isNotNull(); + assertThat(intent.getComponent()).isEqualTo(new ComponentName(pkg, "activity")); + assertThat(intent.getCategories()).containsExactly(CATEGORY_INFO); + assertThat(intent.getFlags()).isEqualTo(FLAG_ACTIVITY_NEW_TASK); + verify(pm).queryIntentActivitiesAsUser( + eqIntent(new Intent(ACTION_MAIN).addCategory(CATEGORY_INFO).setPackage(pkg)), + eqResolveInfoFlags(MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE), + eq(userId)); + } + + public void testGetLaunchIntentForPackage_categoryLauncherActivity_returnsIt() { + String pkg = "com.some.package"; + int userId = 42; + ResolveInfo categoryLauncherResolveInfo1 = new ResolveInfo(); + categoryLauncherResolveInfo1.activityInfo = new ActivityInfo(); + categoryLauncherResolveInfo1.activityInfo.packageName = pkg; + categoryLauncherResolveInfo1.activityInfo.name = "activity1"; + ResolveInfo categoryLauncherResolveInfo2 = new ResolveInfo(); + categoryLauncherResolveInfo2.activityInfo = new ActivityInfo(); + categoryLauncherResolveInfo2.activityInfo.packageName = pkg; + categoryLauncherResolveInfo2.activityInfo.name = "activity2"; + Intent baseIntent = new Intent(ACTION_MAIN).setPackage(pkg); + + final MockedApplicationPackageManager pm = spy(new MockedApplicationPackageManager()); + doReturn(userId).when(pm).getUserId(); + doReturn(List.of()) + .when(pm).queryIntentActivitiesAsUser( + eqIntent(new Intent(baseIntent).addCategory(CATEGORY_INFO)), + any(ResolveInfoFlags.class), + anyInt()); + doReturn( + List.of(categoryLauncherResolveInfo1, categoryLauncherResolveInfo2)) + .when(pm).queryIntentActivitiesAsUser( + eqIntent(new Intent(baseIntent).addCategory(CATEGORY_LAUNCHER)), + any(ResolveInfoFlags.class), + anyInt()); + + Intent intent = pm.getLaunchIntentForPackage(pkg, true); + + assertThat(intent).isNotNull(); + assertThat(intent.getComponent()).isEqualTo(new ComponentName(pkg, "activity1")); + assertThat(intent.getCategories()).containsExactly(CATEGORY_LAUNCHER); + assertThat(intent.getFlags()).isEqualTo(FLAG_ACTIVITY_NEW_TASK); + } + + public void testGetLaunchIntentForPackage_noSuitableActivity_returnsNull() throws Exception { + String pkg = "com.some.package"; + int userId = 42; + + final MockedApplicationPackageManager pm = spy(new MockedApplicationPackageManager()); + doReturn(userId).when(pm).getUserId(); + doReturn(List.of()) + .when(pm).queryIntentActivitiesAsUser( + any(), + any(ResolveInfoFlags.class), + anyInt()); + + Intent intent = pm.getLaunchIntentForPackage(pkg, true); + + assertThat(intent).isNull(); + } + + /** Equality check for intents -- ignoring extras */ + private static Intent eqIntent(Intent wanted) { + return argThat( + new ArgumentMatcher<>() { + @Override + public boolean matches(Intent argument) { + return wanted.filterEquals(argument) + && wanted.getFlags() == argument.getFlags(); + } + + @Override + public String toString() { + return wanted.toString(); + } + }); + } + + private static ResolveInfoFlags eqResolveInfoFlags(long flagsWanted) { + return argThat( + new ArgumentMatcher<>() { + @Override + public boolean matches(ResolveInfoFlags argument) { + return argument.getValue() == flagsWanted; + } + + @Override + public String toString() { + return String.valueOf(flagsWanted); + } + }); + } } diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java index bb059108d4b6..3e6520106ab0 100644 --- a/core/tests/coretests/src/android/os/ParcelTest.java +++ b/core/tests/coretests/src/android/os/ParcelTest.java @@ -29,8 +29,6 @@ import android.util.Log; import androidx.test.ext.junit.runners.AndroidJUnit4; -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -418,63 +416,4 @@ public class ParcelTest { int binderEndPos = pA.dataPosition(); assertTrue(pA.hasBinders(binderStartPos, binderEndPos - binderStartPos)); } - - private static final byte[] TEST_DATA = new byte[] {4, 8, 15, 16, 23, 42}; - - // Allow for some Parcel overhead - private static final int TEST_DATA_LENGTH = TEST_DATA.length + 100; - - @Test - public void testMarshall_ByteBuffer_wrapped() { - ByteBuffer bb = ByteBuffer.allocate(TEST_DATA_LENGTH); - testMarshall_ByteBuffer(bb); - } - - @Test - public void testMarshall_DirectByteBuffer() { - ByteBuffer bb = ByteBuffer.allocateDirect(TEST_DATA_LENGTH); - testMarshall_ByteBuffer(bb); - } - - private void testMarshall_ByteBuffer(ByteBuffer bb) { - // Ensure that Parcel respects the starting offset by not starting at 0 - bb.position(1); - bb.mark(); - - // Parcel test data, then marshall into the ByteBuffer - Parcel p1 = Parcel.obtain(); - p1.writeByteArray(TEST_DATA); - p1.marshall(bb); - p1.recycle(); - - assertTrue(bb.position() > 1); - bb.reset(); - - // Unmarshall test data into a new Parcel - Parcel p2 = Parcel.obtain(); - bb.reset(); - p2.unmarshall(bb); - assertTrue(bb.position() > 1); - p2.setDataPosition(0); - byte[] marshalled = p2.marshall(); - - bb.reset(); - for (int i = 0; i < TEST_DATA.length; i++) { - assertEquals(bb.get(), marshalled[i]); - } - - byte[] testDataCopy = new byte[TEST_DATA.length]; - p2.setDataPosition(0); - p2.readByteArray(testDataCopy); - for (int i = 0; i < TEST_DATA.length; i++) { - assertEquals(TEST_DATA[i], testDataCopy[i]); - } - - // Test that overflowing the buffer throws an exception - bb.reset(); - // Leave certainly not enough room for the test data - bb.limit(bb.position() + TEST_DATA.length - 1); - assertThrows(BufferOverflowException.class, () -> p2.marshall(bb)); - p2.recycle(); - } } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt index 14c15210252a..90011f4018f6 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt @@ -24,6 +24,9 @@ import android.graphics.PointF import android.graphics.Rect import android.os.Handler import android.os.UserManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.view.IWindowManager import android.view.MotionEvent import android.view.View @@ -36,6 +39,7 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService +import com.android.wm.shell.Flags import com.android.wm.shell.R import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.bubbles.Bubble @@ -64,6 +68,10 @@ import com.android.wm.shell.shared.TransactionPool import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils import com.android.wm.shell.shared.bubbles.BubbleBarLocation import com.android.wm.shell.shared.bubbles.DeviceConfig +import com.android.wm.shell.shared.bubbles.DragZone +import com.android.wm.shell.shared.bubbles.DragZoneFactory +import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker.SplitScreenMode +import com.android.wm.shell.shared.bubbles.DraggedObject import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit @@ -88,6 +96,8 @@ class BubbleBarLayerViewTest { const val SCREEN_HEIGHT = 1000 } + @get:Rule val setFlagsRule = SetFlagsRule() + @get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this) private val context = ApplicationProvider.getApplicationContext<Context>() @@ -101,6 +111,7 @@ class BubbleBarLayerViewTest { private lateinit var bgExecutor: TestShellExecutor private lateinit var bubbleLogger: BubbleLogger private lateinit var testBubblesList: MutableList<Bubble> + private lateinit var dragZoneFactory: DragZoneFactory @Before fun setUp() { @@ -134,6 +145,10 @@ class BubbleBarLayerViewTest { whenever(bubbleData.bubbles).thenReturn(testBubblesList) whenever(bubbleData.hasBubbles()).thenReturn(!testBubblesList.isEmpty()) + dragZoneFactory = DragZoneFactory(context, deviceConfig, + { SplitScreenMode.UNSUPPORTED }, + { false }) + bubbleController = createBubbleController( bubbleData, @@ -280,6 +295,7 @@ class BubbleBarLayerViewTest { assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) } + @DisableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_BUBBLE_ANYTHING) @Test fun testEventLogging_dragExpandedViewLeft() { val bubble = createBubble("first") @@ -287,7 +303,7 @@ class BubbleBarLayerViewTest { getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(bubble) - bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true) + bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */) } waitForExpandedViewAnimation() @@ -305,6 +321,7 @@ class BubbleBarLayerViewTest { assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) } + @DisableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_BUBBLE_ANYTHING) @Test fun testEventLogging_dragExpandedViewRight() { val bubble = createBubble("first") @@ -312,7 +329,7 @@ class BubbleBarLayerViewTest { getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(bubble) - bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true) + bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */) } waitForExpandedViewAnimation() @@ -330,6 +347,76 @@ class BubbleBarLayerViewTest { assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) } + @EnableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE) + @Test + fun testEventLogging_dragExpandedViewLeft_bubbleAnything() { + val bubble = createBubble("first") + bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT + + getInstrumentation().runOnMainSync { + bubbleBarLayerView.showExpandedView(bubble) + bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */) + } + waitForExpandedViewAnimation() + + val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view) + assertThat(handleView).isNotNull() + + val dragZones = dragZoneFactory.createSortedDragZones( + DraggedObject.ExpandedView(BubbleBarLocation.RIGHT)) + val rightDragZone = dragZones.filterIsInstance<DragZone.Bubble.Right>().first() + val rightPoint = PointF(rightDragZone.bounds.centerX().toFloat(), + rightDragZone.bounds.centerY().toFloat()) + val leftDragZone = dragZones.filterIsInstance<DragZone.Bubble.Left>().first() + val leftPoint = PointF(leftDragZone.bounds.centerX().toFloat(), + leftDragZone.bounds.centerY().toFloat()) + + // Drag from right to left + handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, rightPoint) + handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, leftPoint) + handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, leftPoint) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.logs[0].eventId) + .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW.id) + assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) + } + + @EnableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE) + @Test + fun testEventLogging_dragExpandedViewRight_bubbleAnything() { + val bubble = createBubble("first") + bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT + + getInstrumentation().runOnMainSync { + bubbleBarLayerView.showExpandedView(bubble) + bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */) + } + waitForExpandedViewAnimation() + + val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view) + assertThat(handleView).isNotNull() + + val dragZones = dragZoneFactory.createSortedDragZones( + DraggedObject.ExpandedView(BubbleBarLocation.LEFT)) + val rightDragZone = dragZones.filterIsInstance<DragZone.Bubble.Right>().first() + val rightPoint = PointF(rightDragZone.bounds.centerX().toFloat(), + rightDragZone.bounds.centerY().toFloat()) + val leftDragZone = dragZones.filterIsInstance<DragZone.Bubble.Left>().first() + val leftPoint = PointF(leftDragZone.bounds.centerX().toFloat(), + leftDragZone.bounds.centerY().toFloat()) + + // Drag from left to right + handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, leftPoint) + handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, rightPoint) + handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, rightPoint) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.logs[0].eventId) + .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW.id) + assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) + } + @Test fun testUpdateExpandedView_updateLocation() { bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT @@ -385,7 +472,7 @@ class BubbleBarLayerViewTest { bubbleLogger, ) // Mark visible so we don't wait for task view before animations can start - bubbleBarExpandedView.onContentVisibilityChanged(true) + bubbleBarExpandedView.onContentVisibilityChanged(true /* visible */) val viewInfo = FakeBubbleFactory.createViewInfo(bubbleBarExpandedView) return FakeBubbleFactory.createChatBubble(context, key, viewInfo).also { 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 9bb51a87c08f..ef30d8965452 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml @@ -34,6 +34,7 @@ android:src="@drawable/bubble_ic_settings"/> <TextView + android:id="@+id/education_manage_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/bubble_popup_text_margin" @@ -45,6 +46,7 @@ android:text="@string/bubble_bar_education_manage_title"/> <TextView + android:id="@+id/education_manage_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/bubble_popup_text_margin" 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 1616707954f5..9076d6a87678 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml @@ -34,6 +34,7 @@ android:src="@drawable/ic_floating_landscape"/> <TextView + android:id="@+id/education_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/bubble_popup_text_margin" @@ -45,6 +46,7 @@ android:text="@string/bubble_bar_education_stack_title"/> <TextView + android:id="@+id/education_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/bubble_popup_text_margin" diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml index 225303b2d942..17ebac95e1dd 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml @@ -40,6 +40,7 @@ android:tint="@color/bubbles_icon_tint"/> <TextView + android:id="@+id/manage_dismiss" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" @@ -67,6 +68,7 @@ android:tint="@color/bubbles_icon_tint"/> <TextView + android:id="@+id/manage_dont_bubble" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 426c3ee5b853..290ef1633819 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -27,6 +27,7 @@ import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; +import static com.android.wm.shell.shared.TypefaceUtils.setTypeface; import android.annotation.NonNull; import android.annotation.SuppressLint; @@ -71,6 +72,7 @@ import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.common.AlphaOptimizedButton; import com.android.wm.shell.shared.TriangleShape; +import com.android.wm.shell.shared.TypefaceUtils; import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; import com.android.wm.shell.taskview.TaskView; @@ -551,6 +553,7 @@ public class BubbleExpandedView extends LinearLayout { mManageButton = (AlphaOptimizedButton) LayoutInflater.from(ctw).inflate( R.layout.bubble_manage_button, this /* parent */, false /* attach */); addView(mManageButton); + setTypeface(mManageButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE); mManageButton.setVisibility(visibility); setManageClickListener(); post(() -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java index da6948d947d8..92007a4df71e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java @@ -50,6 +50,7 @@ import androidx.annotation.Nullable; import com.android.wm.shell.R; import com.android.wm.shell.shared.TriangleShape; +import com.android.wm.shell.shared.TypefaceUtils; /** * Flyout view that appears as a 'chat bubble' alongside the bubble stack. The flyout can visually @@ -165,8 +166,10 @@ public class BubbleFlyoutView extends FrameLayout { LayoutInflater.from(context).inflate(R.layout.bubble_flyout, this, true); mFlyoutTextContainer = findViewById(R.id.bubble_flyout_text_container); mSenderText = findViewById(R.id.bubble_flyout_name); + TypefaceUtils.setTypeface(mSenderText, TypefaceUtils.FontFamily.GSF_LABEL_LARGE); mSenderAvatar = findViewById(R.id.bubble_flyout_avatar); mMessageText = mFlyoutTextContainer.findViewById(R.id.bubble_flyout_text); + TypefaceUtils.setTypeface(mMessageText, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM); final Resources res = getResources(); mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java index 64f54b8ab5be..e901e0c07fe4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java @@ -46,6 +46,7 @@ import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ContrastColorUtil; import com.android.wm.shell.Flags; import com.android.wm.shell.R; +import com.android.wm.shell.shared.TypefaceUtils; import java.util.ArrayList; import java.util.List; @@ -234,6 +235,10 @@ public class BubbleOverflowContainerView extends LinearLayout { setBackgroundColor(bgColor); mEmptyStateTitle.setTextColor(textColor); mEmptyStateSubtitle.setTextColor(textColor); + TypefaceUtils.setTypeface(mEmptyStateTitle, + TypefaceUtils.FontFamily.GSF_BODY_MEDIUM_EMPHASIZED); + TypefaceUtils.setTypeface(mEmptyStateSubtitle, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM); + } public void updateFontSize() { @@ -322,6 +327,7 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V TextView viewName = overflowView.findViewById(R.id.bubble_view_name); viewName.setTextColor(textColor); + TypefaceUtils.setTypeface(viewName, TypefaceUtils.FontFamily.GSF_LABEL_LARGE); return new ViewHolder(overflowView, mPositioner); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index dd5a23aae7f9..3dce45690cf2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -89,6 +89,8 @@ import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout; import com.android.wm.shell.bubbles.animation.StackAnimationController; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.shared.TypefaceUtils; +import com.android.wm.shell.shared.TypefaceUtils.FontFamily; import com.android.wm.shell.shared.animation.Interpolators; import com.android.wm.shell.shared.animation.PhysicsAnimator; import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; @@ -1397,6 +1399,14 @@ public class BubbleStackView extends FrameLayout // The menu itself should respect locale direction so the icons are on the correct side. mManageMenu.setLayoutDirection(LAYOUT_DIRECTION_LOCALE); addView(mManageMenu); + + // Doesn't seem to work unless view is added; so set font after. + TypefaceUtils.setTypeface(findViewById(R.id.manage_dismiss), FontFamily.GSF_LABEL_LARGE); + TypefaceUtils.setTypeface(findViewById(R.id.manage_dont_bubble), + FontFamily.GSF_LABEL_LARGE); + TypefaceUtils.setTypeface(mManageSettingsText, FontFamily.GSF_LABEL_LARGE); + TypefaceUtils.setTypeface(findViewById(R.id.bubble_manage_menu_fullscreen_title), + FontFamily.GSF_LABEL_LARGE); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt index 39a2a7b868a0..d2ad70886fa1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt @@ -27,6 +27,7 @@ import android.widget.Button import android.widget.LinearLayout import com.android.internal.R.color.system_neutral1_900 import com.android.wm.shell.R +import com.android.wm.shell.shared.TypefaceUtils import com.android.wm.shell.shared.animation.Interpolators /** @@ -53,6 +54,12 @@ class ManageEducationView( init { LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this) + TypefaceUtils.setTypeface(findViewById(R.id.user_education_title), + TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED) + TypefaceUtils.setTypeface(findViewById(R.id.user_education_description), + TypefaceUtils.FontFamily.GSF_BODY_MEDIUM) + TypefaceUtils.setTypeface(manageButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE_EMPHASIZED) + TypefaceUtils.setTypeface(gotItButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE_EMPHASIZED) visibility = View.GONE elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt index 16606198b240..9ac059890dd7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt @@ -26,6 +26,7 @@ import android.widget.LinearLayout import android.widget.TextView import com.android.internal.util.ContrastColorUtil import com.android.wm.shell.R +import com.android.wm.shell.shared.TypefaceUtils import com.android.wm.shell.shared.animation.Interpolators /** @@ -59,6 +60,9 @@ class StackEducationView( init { LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this) + TypefaceUtils.setTypeface(titleTextView, + TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED) + TypefaceUtils.setTypeface(descTextView, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM) visibility = View.GONE elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt index 9d4f904e55d0..35435569d8b1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt @@ -203,7 +203,11 @@ class BubbleBarExpandedViewDragController( draggedObject: MagnetizedObject<*>, ) { dragListener.onReleased(inDismiss = true) - pinController.onDragEnd() + if (dropTargetManager != null) { + dropTargetManager.onDragEnded() + } else { + pinController.onDragEnd() + } dismissView.hide() } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 3997412ab459..2cc9387bd1e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -147,15 +147,23 @@ public class BubbleBarLayerView extends FrameLayout Log.w(TAG, "dropped invalid bubble: " + mExpandedBubble); return; } + + final boolean isBubbleLeft = zone instanceof DragZone.Bubble.Left; + final boolean isBubbleRight = zone instanceof DragZone.Bubble.Right; + if (!isBubbleLeft && !isBubbleRight) { + // If we didn't finish the "change" animation make sure to animate + // it back to the right spot + locationChangeListener.onChange(mInitialLocation); + } if (zone instanceof DragZone.FullScreen) { ((Bubble) mExpandedBubble).getTaskView().moveToFullscreen(); // Make sure location change listener is updated with the initial // location -- even if we "switched sides" during the drag, since // we've ended up in fullscreen, the location shouldn't change. locationChangeListener.onRelease(mInitialLocation); - } else if (zone instanceof DragZone.Bubble.Left) { + } else if (isBubbleLeft) { locationChangeListener.onRelease(BubbleBarLocation.LEFT); - } else if (zone instanceof DragZone.Bubble.Right) { + } else if (isBubbleRight) { locationChangeListener.onRelease(BubbleBarLocation.RIGHT); } } @@ -189,7 +197,7 @@ public class BubbleBarLayerView extends FrameLayout @NonNull @Override public SplitScreenMode getSplitScreenMode() { - return SplitScreenMode.NONE; + return SplitScreenMode.UNSUPPORTED; } }, new DragZoneFactory.DesktopWindowModeChecker() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java index 6c14d83dfafa..bccc6dcd91db 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java @@ -25,6 +25,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.wm.shell.R; +import com.android.wm.shell.shared.TypefaceUtils; /** * Bubble bar expanded view menu item view to display menu action details @@ -55,6 +56,7 @@ public class BubbleBarMenuItemView extends LinearLayout { super.onFinishInflate(); mImageView = findViewById(R.id.bubble_bar_menu_item_icon); mTextView = findViewById(R.id.bubble_bar_menu_item_title); + TypefaceUtils.setTypeface(mTextView, TypefaceUtils.FontFamily.GSF_TITLE_MEDIUM); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java index dfbf655bb6fc..7c0f8e138aae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java @@ -33,6 +33,7 @@ import androidx.core.widget.ImageViewCompat; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubble; +import com.android.wm.shell.shared.TypefaceUtils; import java.util.ArrayList; @@ -75,6 +76,7 @@ public class BubbleBarMenuView extends LinearLayout { mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section); mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon); mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title); + TypefaceUtils.setTypeface(mBubbleTitleView, TypefaceUtils.FontFamily.GSF_TITLE_MEDIUM); mBubbleDismissIconView = findViewById(R.id.bubble_bar_manage_menu_dismiss_icon); updateThemeColors(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt index 7adec39c9d66..0bd3a54ceeaa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt @@ -35,6 +35,7 @@ import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME import com.android.wm.shell.bubbles.BubbleEducationController import com.android.wm.shell.bubbles.BubbleViewProvider import com.android.wm.shell.bubbles.setup +import com.android.wm.shell.shared.TypefaceUtils import com.android.wm.shell.shared.animation.PhysicsAnimator import com.android.wm.shell.shared.bubbles.BubblePopupDrawable import com.android.wm.shell.shared.bubbles.BubblePopupView @@ -108,6 +109,10 @@ class BubbleEducationViewController(private val context: Context, private val li root.getBoundsOnScreen(rootBounds) educationView = createEducationView(R.layout.bubble_bar_stack_education, root).apply { + TypefaceUtils.setTypeface(findViewById(R.id.education_title), + TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED) + TypefaceUtils.setTypeface(findViewById(R.id.education_text), + TypefaceUtils.FontFamily.GSF_BODY_MEDIUM) setArrowDirection(BubblePopupDrawable.ArrowDirection.DOWN) updateEducationPosition(view = this, position, rootBounds) val arrowToEdgeOffset = popupDrawable?.config?.cornerRadius ?: 0f @@ -153,6 +158,10 @@ class BubbleEducationViewController(private val context: Context, private val li educationView = createEducationView(R.layout.bubble_bar_manage_education, root).apply { + TypefaceUtils.setTypeface(findViewById(R.id.education_manage_title), + TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED) + TypefaceUtils.setTypeface(findViewById(R.id.education_manage_text), + TypefaceUtils.FontFamily.GSF_BODY_MEDIUM) pivotY = 0f doOnLayout { it.pivotX = it.width / 2f } setOnClickListener { hideEducation(animated = true) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md index dd5827af97d9..320de2ae5945 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md @@ -142,8 +142,8 @@ transaction which is applied. ## Tracing activity starts & finishes in the app process It's sometimes useful to know when to see a stack trace of when an activity starts in the app code -(ie. if you are repro'ing a bug related to activity starts). You can enable this system property to -get this trace: +or via a `WindowContainerTransaction` (ie. if you are repro'ing a bug related to activity starts). +You can enable this system property to get this trace: ```shell # Enabling adb shell setprop persist.wm.debug.start_activity true @@ -168,6 +168,21 @@ adb shell setprop persist.wm.debug.finish_activity \"\" adb reboot ``` +## Tracing transition requests in the Shell + +To trace where a new WM transition is started in the Shell, you can enable this system property: +```shell +# Enabling +adb shell setprop persist.wm.debug.start_shell_transition true +adb reboot +adb logcat -s "ShellTransitions" + +# Disabling +adb shell setprop persist.wm.debug.start_shell_transition \"\" +adb reboot +``` + + ## Dumps Because the Shell library is built as a part of SystemUI, dumping the state is currently done as a diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index cef18f55b86d..c58bb6e3bd31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -40,7 +40,6 @@ import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; -import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; @@ -341,23 +340,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH return false; } - /** - * @return a change representing a config-at-end activity for a given parent. - */ - @Nullable - public TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info, - @android.annotation.NonNull WindowContainerToken parent) { - for (TransitionInfo.Change change : info.getChanges()) { - if (change.getTaskInfo() == null - && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END) - && change.getParent() != null && change.getParent().equals(parent)) { - return change; - } - } - return null; - } - - /** Whether a particular package is same as current pip package. */ public boolean isPackageActiveInPip(@Nullable String packageName) { // No-op, to be handled differently in PIP1 and PIP2 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java index 880e143a0e13..92f36d08a941 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java @@ -43,6 +43,7 @@ import com.android.wm.shell.shared.annotations.ShellMainThread; import java.util.ArrayList; import java.util.List; +import java.util.StringJoiner; /** * A Task Listener implementation used only for CUJs and trigger paths that cannot be initiated via @@ -114,6 +115,17 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, // Set the new params but make sure mPictureInPictureParams is not null. mPictureInPictureParams = params == null ? new PictureInPictureParams.Builder().build() : params; + logRemoteActions(mPictureInPictureParams); + } + + private void logRemoteActions(@android.annotation.NonNull PictureInPictureParams params) { + StringJoiner sj = new StringJoiner("|", "[", "]"); + if (params.hasSetActions()) { + params.getActions().forEach((action) -> sj.add(action.getTitle())); + } + + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "PIP remote actions=%s", sj.toString()); } /** Add a PipParamsChangedCallback listener. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index cfcd56393bc2..5d8d8b685a23 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -76,6 +76,7 @@ import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; import com.android.wm.shell.pip2.animation.PipAlphaAnimator; import com.android.wm.shell.pip2.animation.PipEnterAnimator; import com.android.wm.shell.pip2.phone.transition.PipExpandHandler; +import com.android.wm.shell.pip2.phone.transition.PipTransitionUtils; import com.android.wm.shell.shared.TransitionUtil; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; @@ -387,8 +388,8 @@ public class PipTransition extends PipTransitionController implements mFinishCallback = finishCallback; // We expect the PiP activity as a separate change in a config-at-end transition; // only flings are not using config-at-end for resize bounds changes - TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info, - pipChange.getTaskInfo().getToken()); + TransitionInfo.Change pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange( + info, pipChange.getTaskInfo().getToken()); if (pipActivityChange != null) { // Transform calculations use PiP params by default, so make sure they are null to // default to using bounds for scaling calculations instead. @@ -427,8 +428,8 @@ public class PipTransition extends PipTransitionController implements } // We expect the PiP activity as a separate change in a config-at-end transition. - TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info, - pipChange.getTaskInfo().getToken()); + TransitionInfo.Change pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange( + info, pipChange.getTaskInfo().getToken()); if (pipActivityChange == null) { return false; } @@ -497,8 +498,8 @@ public class PipTransition extends PipTransitionController implements } // We expect the PiP activity as a separate change in a config-at-end transition. - TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info, - pipChange.getTaskInfo().getToken()); + TransitionInfo.Change pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange( + info, pipChange.getTaskInfo().getToken()); if (pipActivityChange == null) { return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java index 01cda6c91108..e562f3340217 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java @@ -67,6 +67,36 @@ public class PipTransitionUtils { } /** + * @return a change representing a config-at-end activity for ancestor. + */ + @Nullable + public static TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info, + @NonNull WindowContainerToken ancestor) { + final TransitionInfo.Change ancestorChange = + PipTransitionUtils.getChangeByToken(info, ancestor); + if (ancestorChange == null) return null; + + // Iterate through changes bottom-to-top, going up the parent chain starting with ancestor. + TransitionInfo.Change lastPipChildChange = ancestorChange; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change == ancestorChange) continue; + + if (change.getParent() != null + && change.getParent().equals(lastPipChildChange.getContainer())) { + // Found a child of the last cached child along the ancestral chain. + lastPipChildChange = change; + if (change.getTaskInfo() == null + && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END)) { + // If this is a config-at-end activity change, then we found the chain leaf. + return change; + } + } + } + return null; + } + + /** * @return the leash to interact with the container this change represents. * @throws NullPointerException if the leash is null. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java index 1853ffa96dfc..320a63a95302 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java @@ -34,6 +34,7 @@ import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip2.phone.transition.PipTransitionUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.unfold.UnfoldTransitionHandler; @@ -132,7 +133,7 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { TransitionInfo.Change pipActivityChange = null; if (pipChange != null) { - pipActivityChange = mPipHandler.getDeferConfigActivityChange( + pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange( info, pipChange.getContainer()); everythingElse.getChanges().remove(pipActivityChange); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index e28a7fa159c5..003ef1d453fc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -39,6 +39,7 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived; import static com.android.window.flags.Flags.ensureWallpaperInTransitions; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS; import static com.android.wm.shell.shared.TransitionUtil.FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; @@ -52,6 +53,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.database.ContentObserver; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; @@ -138,6 +140,10 @@ public class Transitions implements RemoteCallable<Transitions>, ShellCommandHandler.ShellCommandActionHandler { static final String TAG = "ShellTransitions"; + // If set, will print the stack trace for transition starts within the process + static final boolean DEBUG_START_TRANSITION = Build.IS_DEBUGGABLE && + SystemProperties.getBoolean("persist.wm.debug.start_shell_transition", false); + /** Set to {@code true} to enable shell transitions. */ public static final boolean ENABLE_SHELL_TRANSITIONS = getShellTransitEnabled(); public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS @@ -346,10 +352,10 @@ public class Transitions implements RemoteCallable<Transitions>, mShellController = shellController; // The very last handler (0 in the list) should be the default one. mHandlers.add(mDefaultTransitionHandler); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default"); + ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: Default"); // Next lowest priority is remote transitions. mHandlers.add(mRemoteTransitionHandler); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote"); + ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: Remote"); shellInit.addInitCallback(this::onInit, this); mHomeTransitionObserver = homeTransitionObserver; mFocusTransitionObserver = focusTransitionObserver; @@ -439,7 +445,7 @@ public class Transitions implements RemoteCallable<Transitions>, mHandlers.add(handler); // Set initial scale settings. handler.setAnimScaleSetting(mTransitionAnimationScaleSetting); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: %s", + ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: %s", handler.getClass().getSimpleName()); } @@ -691,7 +697,7 @@ public class Transitions implements RemoteCallable<Transitions>, void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady"); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s", + ProtoLog.v(WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s", info.getDebugId(), transitionToken, info.toString(" " /* prefix */)); int activeIdx = findByToken(mPendingTransitions, transitionToken); if (activeIdx < 0) { @@ -753,7 +759,7 @@ public class Transitions implements RemoteCallable<Transitions>, if (tr.isIdle()) continue; hadPreceding = true; // Sleep starts a process of forcing all prior transitions to finish immediately - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + ProtoLog.v(WM_SHELL_TRANSITIONS, "Start finish-for-sync track %d", i); finishForSync(active.mToken, i, null /* forceFinish */); } @@ -797,7 +803,7 @@ public class Transitions implements RemoteCallable<Transitions>, if (info.getRootCount() == 0 && !KeyguardTransitionHandler.handles(info)) { // No root-leashes implies that the transition is empty/no-op, so just do // housekeeping and return. - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots in %s so" + ProtoLog.v(WM_SHELL_TRANSITIONS, "No transition roots in %s so" + " abort", active); onAbort(active); return true; @@ -839,7 +845,7 @@ public class Transitions implements RemoteCallable<Transitions>, && allOccluded)) { // Treat this as an abort since we are bypassing any merge logic and effectively // finishing immediately. - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + ProtoLog.v(WM_SHELL_TRANSITIONS, "Non-visible anim so abort: %s", active); onAbort(active); return true; @@ -873,7 +879,7 @@ public class Transitions implements RemoteCallable<Transitions>, void processReadyQueue(Track track) { if (track.mReadyTransitions.isEmpty()) { if (track.mActiveTransition == null) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Track %d became idle", + ProtoLog.v(WM_SHELL_TRANSITIONS, "Track %d became idle", mTracks.indexOf(track)); if (areTracksIdle()) { if (!mReadyDuringSync.isEmpty()) { @@ -885,7 +891,7 @@ public class Transitions implements RemoteCallable<Transitions>, if (!success) break; } } else if (mPendingTransitions.isEmpty()) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition " + ProtoLog.v(WM_SHELL_TRANSITIONS, "All active transition " + "animations finished"); mKnownTransitions.clear(); // Run all runnables from the run-when-idle queue. @@ -926,7 +932,7 @@ public class Transitions implements RemoteCallable<Transitions>, onMerged(playingToken, readyToken); return; } - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while" + ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition %s ready while" + " %s is still animating. Notify the animating transition" + " in case they can be merged", ready, playing); mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId()); @@ -955,7 +961,7 @@ public class Transitions implements RemoteCallable<Transitions>, } final Track track = mTracks.get(playing.getTrack()); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s", + ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s", merged, playing); int readyIdx = 0; if (track.mReadyTransitions.isEmpty() || track.mReadyTransitions.get(0) != merged) { @@ -996,7 +1002,7 @@ public class Transitions implements RemoteCallable<Transitions>, } private void playTransition(@NonNull ActiveTransition active) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Playing animation for %s", active); + ProtoLog.v(WM_SHELL_TRANSITIONS, "Playing animation for %s", active); final var token = active.mToken; for (int i = 0; i < mObservers.size(); ++i) { @@ -1007,12 +1013,12 @@ public class Transitions implements RemoteCallable<Transitions>, // If a handler already chose to run this animation, try delegating to it first. if (active.mHandler != null) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s", + ProtoLog.v(WM_SHELL_TRANSITIONS, " try firstHandler %s", active.mHandler); boolean consumed = active.mHandler.startAnimation(token, active.mInfo, active.mStartT, active.mFinishT, (wct) -> onFinish(token, wct)); if (consumed) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler"); + ProtoLog.v(WM_SHELL_TRANSITIONS, " animated by firstHandler"); mTransitionTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler); if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { Trace.instant(TRACE_TAG_WINDOW_MANAGER, @@ -1042,14 +1048,14 @@ public class Transitions implements RemoteCallable<Transitions>, ) { for (int i = mHandlers.size() - 1; i >= 0; --i) { if (mHandlers.get(i) == skip) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " skip handler %s", + ProtoLog.v(WM_SHELL_TRANSITIONS, " skip handler %s", mHandlers.get(i)); continue; } boolean consumed = mHandlers.get(i).startAnimation(transition, info, startT, finishT, finishCB); if (consumed) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s", + ProtoLog.v(WM_SHELL_TRANSITIONS, " animated by %s", mHandlers.get(i)); mTransitionTracer.logDispatched(info.getDebugId(), mHandlers.get(i)); if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { @@ -1155,7 +1161,7 @@ public class Transitions implements RemoteCallable<Transitions>, for (int i = 0; i < mObservers.size(); ++i) { mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted); } - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished " + ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition animation finished " + "(aborted=%b), notifying core %s", active.mAborted, active); if (active.mStartT != null) { // Applied by now, so clear immediately to remove any references. Do not set to null @@ -1209,7 +1215,7 @@ public class Transitions implements RemoteCallable<Transitions>, void requestStartTransition(@NonNull IBinder transitionToken, @Nullable TransitionRequestInfo request) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s", + ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s", request.getDebugId(), transitionToken, request); if (mKnownTransitions.containsKey(transitionToken)) { throw new RuntimeException("Transition already started " + transitionToken); @@ -1228,6 +1234,8 @@ public class Transitions implements RemoteCallable<Transitions>, if (requestResult != null) { active.mHandler = requestResult.first; wct = requestResult.second; + ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition (#%d): request handled by %s", + request.getDebugId(), active.mHandler.getClass().getSimpleName()); } if (request.getDisplayChange() != null) { TransitionRequestInfo.DisplayChange change = request.getDisplayChange(); @@ -1273,8 +1281,12 @@ public class Transitions implements RemoteCallable<Transitions>, */ public IBinder startTransition(@WindowManager.TransitionType int type, @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition " + ProtoLog.v(WM_SHELL_TRANSITIONS, "Directly starting a new transition " + "type=%s wct=%s handler=%s", transitTypeToString(type), wct, handler); + if (DEBUG_START_TRANSITION) { + Log.d(TAG, "startTransition: type=" + transitTypeToString(type) + + " wct=" + wct + " handler=" + handler.getClass().getName(), new Throwable()); + } final ActiveTransition active = new ActiveTransition(mOrganizer.startNewTransition(type, wct)); active.mHandler = handler; @@ -1362,7 +1374,7 @@ public class Transitions implements RemoteCallable<Transitions>, } // Attempt to merge a SLEEP info to signal that the playing transition needs to // fast-forward. - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s" + ProtoLog.v(WM_SHELL_TRANSITIONS, " Attempt to merge sync %s" + " into %s via a SLEEP proxy", nextSync, playing); playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT, dummyT, playing.mToken, (wct) -> {}); @@ -1598,7 +1610,7 @@ public class Transitions implements RemoteCallable<Transitions>, public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo, SurfaceControl.Transaction t, SurfaceControl.Transaction finishT) throws RemoteException { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady(transaction=%d)", + ProtoLog.v(WM_SHELL_TRANSITIONS, "onTransitionReady(transaction=%d)", t.getId()); mMainExecutor.execute(() -> Transitions.this.onTransitionReady( iBinder, transitionInfo, t, finishT)); @@ -1784,8 +1796,9 @@ public class Transitions implements RemoteCallable<Transitions>, pw.println(prefix + TAG); final String innerPrefix = prefix + " "; - pw.println(prefix + "Handlers:"); - for (TransitionHandler handler : mHandlers) { + pw.println(prefix + "Handlers (ordered by priority):"); + for (int i = mHandlers.size() - 1; i >= 0; i--) { + final TransitionHandler handler = mHandlers.get(i); pw.print(innerPrefix); pw.print(handler.getClass().getSimpleName()); pw.println(" (" + Integer.toHexString(System.identityHashCode(handler)) + ")"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index bcf9396ff0c1..989550c60c0d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -1756,8 +1756,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId); disposeResizeVeil(); disposeStatusBarInputLayer(); - mWindowDecorViewHolder.close(); - mWindowDecorViewHolder = null; + if (mWindowDecorViewHolder != null) { + mWindowDecorViewHolder.close(); + mWindowDecorViewHolder = null; + } if (canEnterDesktopMode(mContext) && isEducationEnabled()) { notifyNoCaptionHandle(); } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt index 509f4f202b6b..8e1cf167318e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt @@ -254,6 +254,16 @@ fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider( } } +/** + * Checks that surfaces are still within the expected region after snapping to a snap point. + * + * @param component The component we are checking (should be one of the two split apps) + * @param landscapePosLeft If [true], and device is in left/right split, app is on the left side of + * the screen. Has no meaning if device is in top/bottom split. + * @param portraitPosTop If [true], and device is in top/bottom split, app is on the top side of + * the screen. Has no meaning if device is in left/right split. + * @param rotation The rotation state of the display. + */ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( component: IComponentMatcher, landscapePosLeft: Boolean, @@ -268,10 +278,12 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( visibleRegion(component).isNotEmpty() visibleRegion(component) .coversAtMost( + // TODO (b/403082705): Should use the new method for determining left/right split. if (displayBounds.width() > displayBounds.height()) { if (landscapePosLeft) { Region( - 0, + // TODO (b/403304310): Check if we're in an offscreen-enabled mode. + -displayBounds.right, // the receding app can go offscreen 0, (dividerRegion.left + dividerRegion.right) / 2, displayBounds.bottom @@ -280,7 +292,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( Region( (dividerRegion.left + dividerRegion.right) / 2, 0, - displayBounds.right, + displayBounds.right * 2, // the receding app can go offscreen displayBounds.bottom ) } @@ -288,7 +300,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( if (portraitPosTop) { Region( 0, - 0, + -displayBounds.bottom, // the receding app can go offscreen displayBounds.right, (dividerRegion.top + dividerRegion.bottom) / 2 ) @@ -297,7 +309,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( 0, (dividerRegion.top + dividerRegion.bottom) / 2, displayBounds.right, - displayBounds.bottom + displayBounds.bottom * 2 // the receding app can go offscreen ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt index 49d6877a1654..e4183f16ba14 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt @@ -310,12 +310,18 @@ object SplitScreenUtils { } } + /** + * Drags the divider, then releases, making it snap to a new snap point. + */ fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) { + // Find the first display that is turned on (making the assumption that there is only one). val displayBounds = - wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace - ?: error("Display not found") + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual && it.isOn } + ?.layerStackSpace ?: error("Display not found") val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) - dividerBar.drag(Point(displayBounds.width() * 1 / 3, displayBounds.height() * 2 / 3), 200) + // Drag to a point on the lower left of the screen -- this will cause the divider to snap + // to the left- or bottom-side snap point, shrinking the "primary" test app. + dividerBar.drag(Point(displayBounds.width() * 1 / 4, displayBounds.height() * 3 / 4), 200) wmHelper .StateSyncBuilder() diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index d3fc91b65829..b3badd0bd51d 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -203,4 +203,11 @@ flag { description: "Initialize GraphicBufferAllocater on ViewRootImpl init, to avoid blocking on init during buffer allocation, improving app launch latency." bug: "389908734" is_fixed_read_only: true +} + +flag { + name: "bitmap_parcel_ashmem_as_immutable" + namespace: "system_performance" + description: "Whether to parcel implicit copies of bitmaps to ashmem as immutable" + bug: "400807118" }
\ No newline at end of file diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp index 27d4ac7cef4b..104ece6582f5 100644 --- a/libs/hwui/jni/Bitmap.cpp +++ b/libs/hwui/jni/Bitmap.cpp @@ -28,8 +28,18 @@ #include "SkRefCnt.h" #include "SkStream.h" #include "SkTypes.h" +#include "android/binder_parcel.h" #include "android_nio_utils.h" +#ifdef __ANDROID__ +#include <com_android_graphics_hwui_flags.h> +namespace hwui_flags = com::android::graphics::hwui::flags; +#else +namespace hwui_flags { +constexpr bool bitmap_parcel_ashmem_as_immutable() { return false; } +} +#endif + #define DEBUG_PARCEL 0 static jclass gBitmap_class; @@ -841,6 +851,23 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { #endif } +// Returns whether this bitmap should be written to the parcel as mutable. +static bool shouldParcelAsMutable(SkBitmap& bitmap, AParcel* parcel) { + // If the bitmap is immutable, then parcel as immutable. + if (bitmap.isImmutable()) { + return false; + } + + if (!hwui_flags::bitmap_parcel_ashmem_as_immutable()) { + return true; + } + + // If we're going to copy the bitmap to ashmem and write that to the parcel, + // then parcel as immutable, since we won't be mutating the bitmap after + // writing it to the parcel. + return !shouldUseAshmem(parcel, bitmap.computeByteSize()); +} + static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, jint density, jobject parcel) { #ifdef __linux__ // Only Linux support parcel @@ -855,7 +882,7 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, j auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle); bitmapWrapper->getSkBitmap(&bitmap); - p.writeInt32(!bitmap.isImmutable()); + p.writeInt32(shouldParcelAsMutable(bitmap, p.get())); p.writeInt32(bitmap.colorType()); p.writeInt32(bitmap.alphaType()); SkColorSpace* colorSpace = bitmap.colorSpace(); diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index dc669a5eca73..aa8cbd1f0703 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -175,6 +175,8 @@ bool SkiaPipeline::setupMultiFrameCapture() { if (stream->isValid()) { mOpenMultiPicStream = std::move(stream); mSerialContext.reset(new SkSharingSerialContext()); + // passing the GrDirectContext to the SerialContext allows us to raster/serialize GPU images + mSerialContext->setDirectContext(mRenderThread.getGrContext()); SkSerialProcs procs; procs.fImageProc = SkSharingSerialContext::serializeImage; procs.fImageCtx = mSerialContext.get(); diff --git a/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.xml b/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.xml new file mode 100644 index 000000000000..6b534aa9647d --- /dev/null +++ b/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="34dp" + android:height="42dp" + android:viewportWidth="34" + android:viewportHeight="42"> + <path + android:pathData="M0.856,17.569C0.887,19.083 1.004,20.593 1.206,22.094C2.166,28.584 5.804,35.937 15.774,41.089C16.171,41.293 16.61,41.4 17.056,41.4C17.503,41.4 17.942,41.293 18.339,41.089C28.309,35.936 31.947,28.583 32.907,22.093C33.109,20.593 33.226,19.083 33.256,17.569V8.605C33.257,7.919 33.046,7.25 32.652,6.688C32.259,6.127 31.703,5.7 31.059,5.467L18.191,0.8C17.458,0.534 16.655,0.534 15.922,0.8L3.054,5.467C2.41,5.7 1.854,6.127 1.461,6.688C1.067,7.25 0.856,7.919 0.856,8.605V17.569Z" + android:fillColor="#D1C2CB"/> + <path + android:pathData="M15.067,24.333V10.733H18.933V24.333H15.067ZM15.067,31.267V27.433H18.933V31.267H15.067Z" + android:fillColor="@color/settingslib_materialColorSurfaceContainerLowest"/> +</vector>
\ No newline at end of file diff --git a/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml b/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml index 54860d4af20f..deda2586c2e0 100644 --- a/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml +++ b/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml @@ -21,6 +21,7 @@ <enum name="low" value="1"/> <enum name="medium" value="2"/> <enum name="high" value="3"/> + <enum name="off" value="4"/> </attr> <attr name="buttonLevel" format="enum"> <enum name="generic" value="0"/> diff --git a/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml b/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml index 19181dd55852..abc458bc1e27 100644 --- a/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml +++ b/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml @@ -22,4 +22,5 @@ <color name="settingslib_expressive_color_status_level_medium">#FCBD00</color> <!-- static palette red50 --> <color name="settingslib_expressive_color_status_level_high">#DB372D</color> + <color name="settingslib_expressive_color_status_level_off">#D1C2CB</color> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt index 1f8cfb5e432e..eda281c07053 100644 --- a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt +++ b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt @@ -40,7 +40,8 @@ class StatusBannerPreference @JvmOverloads constructor( GENERIC, LOW, MEDIUM, - HIGH + HIGH, + OFF } var iconLevel: BannerStatus = BannerStatus.GENERIC set(value) { @@ -87,6 +88,7 @@ class StatusBannerPreference @JvmOverloads constructor( 1 -> BannerStatus.LOW 2 -> BannerStatus.MEDIUM 3 -> BannerStatus.HIGH + 4 -> BannerStatus.OFF else -> BannerStatus.GENERIC } @@ -104,7 +106,10 @@ class StatusBannerPreference @JvmOverloads constructor( } (holder.findViewById(R.id.status_banner_button) as? MaterialButton)?.apply { - setBackgroundColor(getBackgroundColor(buttonLevel)) + setBackgroundColor( + if (buttonLevel == BannerStatus.OFF) getBackgroundColor(BannerStatus.GENERIC) + else getBackgroundColor(buttonLevel) + ) text = buttonText setOnClickListener(listener) visibility = if (listener != null) View.VISIBLE else View.GONE @@ -143,6 +148,11 @@ class StatusBannerPreference @JvmOverloads constructor( R.color.settingslib_expressive_color_status_level_high ) + BannerStatus.OFF -> ContextCompat.getColor( + context, + R.color.settingslib_expressive_color_status_level_off + ) + else -> ContextCompat.getColor( context, com.android.settingslib.widget.theme.R.color.settingslib_materialColorPrimary @@ -167,6 +177,11 @@ class StatusBannerPreference @JvmOverloads constructor( R.drawable.settingslib_expressive_icon_status_level_high ) + BannerStatus.OFF -> ContextCompat.getDrawable( + context, + R.drawable.settingslib_expressive_icon_status_level_off + ) + else -> null } } @@ -188,6 +203,7 @@ class StatusBannerPreference @JvmOverloads constructor( R.drawable.settingslib_expressive_background_level_high ) + // GENERIC and OFF are using the same background drawable. else -> ContextCompat.getDrawable( context, R.drawable.settingslib_expressive_background_generic diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 55f7317f25e4..758ad797f761 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -1015,6 +1015,10 @@ <uses-permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE" /> <uses-permission android:name="android.permission.READ_COLOR_ZONES" /> + <!-- Permissions required for CTS test - CtsModernMediaProviderTests --> + <uses-permission android:name="com.android.providers.media.permission.ACCESS_OEM_METADATA" /> + <uses-permission android:name="com.android.providers.media.permission.UPDATE_OEM_METADATA" /> + <!-- Permission required for trade-in mode testing --> <uses-permission android:name="android.permission.ENTER_TRADE_IN_MODE" /> diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 1362ffebe107..86559fd557d6 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -1,22 +1,6 @@ { - // Curious where your @Scenario tests are running? - // - // @Ignore: Will not run in any configuration - // - // @FlakyTest: Tests that don't block pre/postsubmit but are staged to run known failures. - // Tests will run in postsubmit on sysui-e2e-staged suite. - // - // - // @PlatinumTest: Marking your test with this annotation will put your tests in presubmit. - // Please DO NOT annotate new or old tests with @PlatinumTest annotation - // without discussing with mdb:android-platinum - // - // @Postsubmit: Do not use this annotation for e2e tests. This won't have any affect. - - // For all other e2e tests which are not platinum, they run in sysui-silver suite,that - // primarily runs in postsubmit with an exception to e2e test related changes. - // If you want to see one shot place to monitor all e2e tests, look for - // sysui-e2e-staged suite. + // Test mappings for SystemUI unit tests. + // For e2e mappings, see go/sysui-e2e-test-mapping // v2/android-virtual-infra/test_mapping/presubmit-avd "presubmit": [ diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 4693377654f8..436e92bc0efa 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -398,13 +398,6 @@ flag { } flag { - name: "light_reveal_migration" - namespace: "systemui" - description: "Move LightRevealScrim to recommended architecture" - bug: "281655028" -} - -flag { name: "theme_overlay_controller_wakefulness_deprecation" namespace: "systemui" description: "Replacing WakefulnessLifecycle by KeyguardTransitionInteractor in " @@ -2136,3 +2129,14 @@ flag { description: "Enables return animations for status bar chips" bug: "202516970" } + +flag { + name: "media_projection_grey_error_text" + namespace: "systemui" + description: "Set the error text color to grey when app sharing is hidden by the requesting app" + bug: "390624334" + metadata { + purpose: PURPOSE_BUGFIX + } +} + diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java index ca94482b9c5a..21ec89646f0f 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java @@ -245,6 +245,17 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner runner.onAnimationCancelled(); finishRunnable.run(); } + + @Override + public void onTransitionConsumed(IBinder transition, boolean aborted) + throws RemoteException { + // Notify the remote runner that the transition has been canceled if the transition + // was merged into another transition or aborted + synchronized (mFinishRunnables) { + mFinishRunnables.remove(transition); + } + runner.onAnimationCancelled(); + } }; } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt index 82e5f5bb6dc8..cd9fcefcdda3 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt @@ -127,6 +127,8 @@ import kotlin.math.min * * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen * @sample com.android.systemui.compose.gallery.DialogLaunchScreen + * @param defaultMinSize true if a default minimum size should be enforced even if this Expandable + * isn't currently clickable and false otherwise. */ @Composable fun Expandable( @@ -140,6 +142,7 @@ fun Expandable( // TODO(b/285250939): Default this to true then remove once the Compose QS expandables have // proven that the new implementation is robust. useModifierBasedImplementation: Boolean = false, + defaultMinSize: Boolean = true, transitionControllerFactory: ComposableControllerFactory? = null, content: @Composable (Expandable) -> Unit, ) { @@ -155,6 +158,7 @@ fun Expandable( onClick, interactionSource, useModifierBasedImplementation, + defaultMinSize, content, ) } @@ -182,6 +186,8 @@ fun Expandable( * * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen * @sample com.android.systemui.compose.gallery.DialogLaunchScreen + * @param defaultMinSize true if a default minimum size should be enforced even if this Expandable + * isn't currently clickable and false otherwise. */ @Composable fun Expandable( @@ -192,6 +198,7 @@ fun Expandable( // TODO(b/285250939): Default this to true then remove once the Compose QS expandables have // proven that the new implementation is robust. useModifierBasedImplementation: Boolean = false, + defaultMinSize: Boolean = true, content: @Composable (Expandable) -> Unit, ) { val controller = controller as ExpandableControllerImpl @@ -209,7 +216,12 @@ fun Expandable( if (useModifierBasedImplementation) { Box(modifier.expandable(controller, onClick, interactionSource)) { - WrappedContent(controller.expandable, controller.contentColor, content) + WrappedContent( + controller.expandable, + controller.contentColor, + defaultMinSize = defaultMinSize, + content, + ) } return } @@ -221,7 +233,7 @@ fun Expandable( val wrappedContent = remember(content) { movableContentOf { expandable: Expandable -> - WrappedContent(expandable, contentColor, content) + WrappedContent(expandable, contentColor, defaultMinSize = defaultMinSize, content) } } @@ -306,21 +318,24 @@ fun Expandable( private fun WrappedContent( expandable: Expandable, contentColor: Color, + defaultMinSize: Boolean, content: @Composable (Expandable) -> Unit, ) { val minSizeContent = @Composable { - // We make sure that the content itself (wrapped by the background) is at least 40.dp, - // which is the same as the M3 buttons. This applies even if onClick is null, to make it - // easier to write expandables that are sometimes clickable and sometimes not. There - // shouldn't be any Expandable smaller than 40dp because if the expandable is not - // clickable directly, then something in its content should be (and with a size >= - // 40dp). - val minSize = 40.dp - Box( - Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize), - contentAlignment = Alignment.Center, - ) { + if (defaultMinSize) { + // We make sure that the content itself (wrapped by the background) is at + // least 40.dp, which is the same as the M3 buttons. This applies even if + // onClick is null, to make it easier to write expandables that are + // sometimes clickable and sometimes not. + val minSize = 40.dp + Box( + modifier = Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize), + contentAlignment = Alignment.Center, + ) { + content(expandable) + } + } else { content(expandable) } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt index 2ea9c487c27c..362748ec71b0 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt @@ -112,6 +112,16 @@ interface NestedDraggable { interface Controller { /** + * Whether this controller is ready to drag. [onDrag] will be called only if this returns + * `true`, and any drag event will be ignored until then. + * + * This can for instance be used to wait for the content we are dragging to to be composed + * before actually dragging, reducing perceivable jank at the beginning of a drag. + */ + val isReadyToDrag: Boolean + get() = true + + /** * Whether drags that were started from nested scrolls should be automatically * [stopped][onDragStopped] as soon as they don't consume the entire `delta` passed to * [onDrag]. @@ -274,6 +284,9 @@ private class NestedDraggableNode( /** The pointers currently down, in order of which they were done and mapping to their type. */ private val pointersDown = linkedMapOf<PointerId, PointerType>() + /** Whether the next drag event should be ignored. */ + private var ignoreNextDrag = false + init { delegate(nestedScrollModifierNode(this, nestedScrollDispatcher)) } @@ -426,6 +439,7 @@ private class NestedDraggableNode( velocityTracker: VelocityTracker, ) { velocityTracker.addPointerInputChange(change) + if (shouldIgnoreDrag(controller)) return scrollWithOverscroll(delta.toOffset()) { deltaFromOverscroll -> scrollWithNestedScroll(deltaFromOverscroll) { deltaFromNestedScroll -> @@ -434,6 +448,23 @@ private class NestedDraggableNode( } } + private fun shouldIgnoreDrag(controller: NestedDraggable.Controller): Boolean { + return when { + !controller.isReadyToDrag -> { + // The controller is not ready yet, so we are waiting for an expensive frame to be + // composed. We should ignore this drag and the next one, given that the first delta + // after an expensive frame will be large. + ignoreNextDrag = true + true + } + ignoreNextDrag -> { + ignoreNextDrag = false + true + } + else -> false + } + } + private fun onDragStopped(controller: NestedDraggable.Controller, velocity: Velocity) { // We launch in the scope of the dispatcher so that the fling is not cancelled if this node // is removed right after onDragStopped() is called. @@ -617,6 +648,8 @@ private class NestedDraggableNode( } private fun scrollWithOverscroll(controller: NestedScrollController, offset: Offset): Offset { + if (shouldIgnoreDrag(controller.controller)) return offset + return scrollWithOverscroll(offset) { delta -> val available = delta.toFloat() val consumed = controller.controller.onDrag(available) diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt index b247993de4e4..b31617369cdb 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt @@ -972,6 +972,36 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw } @Test + fun isReadyToDrag() { + var isReadyToDrag by mutableStateOf(false) + val draggable = TestDraggable(isReadyToDrag = { isReadyToDrag }) + val touchSlop = + rule.setContentWithTouchSlop { + Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation)) + } + + rule.onRoot().performTouchInput { + down(center) + moveBy((touchSlop + 10f).toOffset()) + } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragDelta).isEqualTo(0f) + + rule.onRoot().performTouchInput { moveBy(20f.toOffset()) } + assertThat(draggable.onDragDelta).isEqualTo(0f) + + // Flag as ready to drag. We still ignore the next drag after that. + isReadyToDrag = true + rule.onRoot().performTouchInput { moveBy(30f.toOffset()) } + assertThat(draggable.onDragDelta).isEqualTo(0f) + + // Now we drag. + rule.onRoot().performTouchInput { moveBy(40f.toOffset()) } + assertThat(draggable.onDragDelta).isEqualTo(40f) + } + + @Test fun consumeNestedPreScroll() { var consumeNestedPreScroll by mutableStateOf(false) val draggable = TestDraggable(shouldConsumeNestedPreScroll = { consumeNestedPreScroll }) @@ -1060,6 +1090,7 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw }, private val shouldConsumeNestedPostScroll: (Float) -> Boolean = { true }, private val shouldConsumeNestedPreScroll: (Float) -> Boolean = { false }, + private val isReadyToDrag: () -> Boolean = { true }, private val autoStopNestedDrags: Boolean = false, ) : NestedDraggable { var shouldStartDrag = true @@ -1092,6 +1123,9 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw return object : NestedDraggable.Controller { override val autoStopNestedDrags: Boolean = this@TestDraggable.autoStopNestedDrags + override val isReadyToDrag: Boolean + get() = isReadyToDrag() + override fun onDrag(delta: Float): Float { onDragCalled = true onDragDelta += delta diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 3150e94908cd..2b8fe39c4870 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -47,6 +47,7 @@ import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState import com.android.compose.animation.scene.transitions import com.android.compose.modifiers.thenIf +import com.android.systemui.Flags import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys @@ -104,7 +105,9 @@ val sceneTransitionsV2 = transitions { fade(Communal.Elements.Grid) fade(Communal.Elements.IndicationArea) fade(Communal.Elements.LockIcon) - fade(Communal.Elements.StatusBar) + if (!Flags.glanceableHubV2()) { + fade(Communal.Elements.StatusBar) + } } timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) } } @@ -131,7 +134,9 @@ val sceneTransitions = transitions { fade(Communal.Elements.Grid) fade(Communal.Elements.IndicationArea) fade(Communal.Elements.LockIcon) - fade(Communal.Elements.StatusBar) + if (!Flags.glanceableHubV2()) { + fade(Communal.Elements.StatusBar) + } } timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt index 2d03e2bcdd19..0181928317e1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.android.compose.animation.scene.ContentScope +import com.android.systemui.Flags import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection @@ -70,8 +71,10 @@ constructor( content = { Box(modifier = Modifier.fillMaxSize()) { with(communalPopupSection) { Popup() } - with(ambientStatusBarSection) { - AmbientStatusBar(modifier = Modifier.fillMaxWidth().zIndex(1f)) + if (!Flags.glanceableHubV2()) { + with(ambientStatusBarSection) { + AmbientStatusBar(modifier = Modifier.fillMaxWidth().zIndex(1f)) + } } CommunalHub( viewModel = viewModel, 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 7782705d4c61..336f9e1ad6e3 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 @@ -38,6 +38,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme @@ -81,6 +82,7 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.flags.QSComposeFragment +import com.android.systemui.qs.flags.QsInCompose import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel @@ -388,6 +390,7 @@ private fun NewChangesDot(modifier: Modifier = Modifier) { } /** A larger button with an icon, some text and an optional dot (to indicate new changes). */ +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable private fun TextButton( icon: Icon, @@ -422,10 +425,13 @@ private fun TextButton( Text( text, Modifier.weight(1f), - style = MaterialTheme.typography.bodyMedium, - // TODO(b/242040009): Remove this letter spacing. We should only use the M3 text - // styles without modifying them. - letterSpacing = 0.01.em, + style = + if (QsInCompose.isEnabled) { + MaterialTheme.typography.labelLarge + } else { + MaterialTheme.typography.bodyMedium + }, + letterSpacing = if (QsInCompose.isEnabled) 0.em else 0.01.em, color = colorAttr(R.attr.onShadeInactiveVariant), maxLines = 1, overflow = TextOverflow.Ellipsis, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index 4b3ebc2bd53d..da54cb8e4679 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -58,6 +58,7 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.disabled import androidx.compose.ui.semantics.progressBarRangeInfo +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.setProgress import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.dp @@ -106,7 +107,10 @@ fun VolumeSlider( return } - Column(modifier = modifier.animateContentSize(), verticalArrangement = Arrangement.Top) { + Column( + modifier = modifier.animateContentSize().semantics(true) {}, + verticalArrangement = Arrangement.Top, + ) { Row( horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth().height(40.dp), @@ -123,7 +127,7 @@ fun VolumeSlider( text = state.label, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.weight(1f), + modifier = Modifier.weight(1f).clearAndSetSemantics {}, ) button?.invoke() } @@ -134,12 +138,11 @@ fun VolumeSlider( onValueChanged = onValueChange, onValueChangeFinished = { onValueChangeFinished?.invoke() }, isEnabled = state.isEnabled, - stepDistance = state.a11yStep, + stepDistance = state.step, accessibilityParams = AccessibilityParams( - label = state.label, - disabledMessage = state.disabledMessage, - currentStateDescription = state.a11yStateDescription, + contentDescription = state.a11yContentDescription, + stateDescription = state.a11yStateDescription, ), haptics = hapticsViewModelFactory?.let { @@ -169,7 +172,7 @@ fun VolumeSlider( text = disabledMessage, color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.labelSmall, - modifier = Modifier.basicMarquee(), + modifier = Modifier.basicMarquee().clearAndSetSemantics {}, ) } } @@ -229,7 +232,7 @@ private fun LegacyVolumeSlider( } val newValue = - (value + targetDirection * state.a11yStep).coerceIn( + (value + targetDirection * state.step).coerceIn( state.valueRange.start, state.valueRange.endInclusive, ) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt index b04d89d8160f..0b0df06c2015 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt @@ -20,6 +20,7 @@ import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.SpringSpec import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.runtime.withFrameNanos import com.android.compose.animation.scene.content.state.TransitionState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -47,6 +48,11 @@ internal fun CoroutineScope.animateContent( oneOffAnimation.animatable = it } + if (layoutState.deferTransitionProgress) { + // Defer the animation by one frame so that the transition progress is changed only when + // the expensive first composition frame is done. + withFrameNanos {} + } animatable.animateTo(targetProgress, animationSpec, initialVelocity) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 024ca22069ae..36eafa400090 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -290,6 +290,15 @@ private class DragControllerImpl( val isDrivingTransition: Boolean get() = layoutState.transitionState == swipeAnimation.contentTransition + override val isReadyToDrag: Boolean + get() { + return !layoutState.deferTransitionProgress || + with(draggableHandler.layoutImpl.elementStateScope) { + swipeAnimation.fromContent.targetSize() != null && + swipeAnimation.toContent.targetSize() != null + } + } + init { check(!isDrivingTransition) { "Multiple controllers with the same SwipeTransition" } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 4da83c3a6fc9..a8b676d4ee45 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -462,7 +462,9 @@ internal class SceneTransitionLayoutImpl( // swipes. .swipeToScene(horizontalDraggableHandler) .swipeToScene(verticalDraggableHandler) - .then(LayoutElement(layoutImpl = this)) + .then( + LayoutElement(layoutImpl = this, transitionState = this.state.transitionState) + ) ) { LookaheadScope { if (_lookaheadScope == null) { @@ -623,23 +625,28 @@ internal class SceneTransitionLayoutImpl( @VisibleForTesting internal fun overlaysOrNullForTest(): Map<OverlayKey, Overlay>? = _overlays } -private data class LayoutElement(private val layoutImpl: SceneTransitionLayoutImpl) : - ModifierNodeElement<LayoutNode>() { - override fun create(): LayoutNode = LayoutNode(layoutImpl) +private data class LayoutElement( + private val layoutImpl: SceneTransitionLayoutImpl, + private val transitionState: TransitionState, +) : ModifierNodeElement<LayoutNode>() { + override fun create(): LayoutNode = LayoutNode(layoutImpl, transitionState) override fun update(node: LayoutNode) { node.layoutImpl = layoutImpl + node.transitionState = transitionState } } -private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) : - Modifier.Node(), ApproachLayoutModifierNode, LayoutAwareModifierNode { +private class LayoutNode( + var layoutImpl: SceneTransitionLayoutImpl, + var transitionState: TransitionState, +) : Modifier.Node(), ApproachLayoutModifierNode, LayoutAwareModifierNode { override fun onRemeasured(size: IntSize) { layoutImpl.lastSize = size } override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean { - return layoutImpl.state.isTransitioning() + return transitionState is TransitionState.Transition.ChangeScene } @ExperimentalComposeUiApi @@ -652,8 +659,7 @@ private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) : val width: Int val height: Int - val transition = - layoutImpl.state.currentTransition as? TransitionState.Transition.ChangeScene + val transition = transitionState as? TransitionState.Transition.ChangeScene if (transition == null) { width = placeable.width height = placeable.height @@ -662,6 +668,9 @@ private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) : val fromSize = layoutImpl.scene(transition.fromScene).targetSize val toSize = layoutImpl.scene(transition.toScene).targetSize + check(fromSize != Element.SizeUnspecified) { "fromSize is unspecified " } + check(toSize != Element.SizeUnspecified) { "toSize is unspecified" } + // Optimization: make sure we don't read state.progress if fromSize == // toSize to avoid running this code every frame when the layout size does // not change. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 56e8c458ad67..4e28dd569f21 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -234,6 +234,10 @@ sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState * `from` overlay by `to` overlay. * @param stateLinks the [StateLink] connecting this [SceneTransitionLayoutState] to other * [SceneTransitionLayoutState]s. + * @param deferTransitionProgress whether we should wait for the first composition to be done before + * changing the progress of a transition. This can help reduce perceivable jank at the start of a + * transition in case the first composition of a content takes a lot of time and we are going to + * miss that first frame. */ fun MutableSceneTransitionLayoutState( initialScene: SceneKey, @@ -246,6 +250,9 @@ fun MutableSceneTransitionLayoutState( canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true }, onTransitionStart: (TransitionState.Transition) -> Unit = {}, onTransitionEnd: (TransitionState.Transition) -> Unit = {}, + + // TODO(b/400688335): Turn on by default and remove this flag before flexiglass is released. + deferTransitionProgress: Boolean = false, ): MutableSceneTransitionLayoutState { return MutableSceneTransitionLayoutStateImpl( initialScene, @@ -258,6 +265,7 @@ fun MutableSceneTransitionLayoutState( canReplaceOverlay, onTransitionStart, onTransitionEnd, + deferTransitionProgress, ) } @@ -272,6 +280,9 @@ fun rememberMutableSceneTransitionLayoutState( canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true }, onTransitionStart: (TransitionState.Transition) -> Unit = {}, onTransitionEnd: (TransitionState.Transition) -> Unit = {}, + + // TODO(b/400688335): Turn on by default and remove this flag before flexiglass is released. + deferTransitionProgress: Boolean = false, ): MutableSceneTransitionLayoutState { val motionScheme = MaterialTheme.motionScheme val layoutState = remember { @@ -286,6 +297,7 @@ fun rememberMutableSceneTransitionLayoutState( canReplaceOverlay = canReplaceOverlay, onTransitionStart = onTransitionStart, onTransitionEnd = onTransitionEnd, + deferTransitionProgress = deferTransitionProgress, ) } @@ -298,6 +310,7 @@ fun rememberMutableSceneTransitionLayoutState( layoutState.canReplaceOverlay = canReplaceOverlay layoutState.onTransitionStart = onTransitionStart layoutState.onTransitionEnd = onTransitionEnd + layoutState.deferTransitionProgress = deferTransitionProgress } return layoutState } @@ -317,6 +330,8 @@ internal class MutableSceneTransitionLayoutStateImpl( }, internal var onTransitionStart: (TransitionState.Transition) -> Unit = {}, internal var onTransitionEnd: (TransitionState.Transition) -> Unit = {}, + // TODO(b/400688335): Turn on by default and remove this flag before flexiglass is released. + internal var deferTransitionProgress: Boolean = false, ) : MutableSceneTransitionLayoutState { private val creationThread: Thread = Thread.currentThread() diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt index 2d2a81542f84..5d4232d8a8b7 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt @@ -38,7 +38,7 @@ internal class ElementStateScopeImpl(private val layoutImpl: SceneTransitionLayo } override fun ContentKey.targetSize(): IntSize? { - return layoutImpl.content(this).targetSize.takeIf { it != IntSize.Zero } + return layoutImpl.content(this).targetSize.takeIf { it != Element.SizeUnspecified } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt index 90bf92ae1dd0..7492f3737edc 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt @@ -93,9 +93,10 @@ internal sealed class Content( val containerState = ContainerState() // Important: All fields in this class should be backed by State given that contents are updated - // directly during composition, outside of a SideEffect. + // directly during composition, outside of a SideEffect, or are observed during composition, + // layout or drawing. var content by mutableStateOf(content) - var targetSize by mutableStateOf(IntSize.Zero) + var targetSize by mutableStateOf(Element.SizeUnspecified) var userActions by mutableStateOf(actions) var zIndex by mutableFloatStateOf(zIndex) @@ -212,9 +213,17 @@ private class ContentNode( return if (isElevationPossible) delegate(ContainerNode(content.containerState)) else null } + override fun onDetach() { + this.content.targetSize = Element.SizeUnspecified + } + fun update(content: Content, isElevationPossible: Boolean, isInvisible: Boolean) { - if (content != this.content || isElevationPossible != this.isElevationPossible) { + if (content != this.content) { + this.content.targetSize = Element.SizeUnspecified this.content = content + } + + if (content != this.content || isElevationPossible != this.isElevationPossible) { this.isElevationPossible = isElevationPossible containerDelegate?.let { undelegate(it) } diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json index 5dbb01338090..770f85969ed1 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json @@ -76,7 +76,7 @@ }, { "x": 5.2, - "y": 5.6 + "y": 5.2 }, { "x": 5.2, @@ -326,11 +326,11 @@ }, { "width": 150, - "height": 293.2 + "height": 292.8 }, { "width": 150, - "height": 293.2 + "height": 292.8 }, { "width": 150, diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json index 1543d186ea03..6b7de56c1da6 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json @@ -81,8 +81,8 @@ "y": 5.2 }, { - "x": 5.6, - "y": 6.8 + "x": 5.2, + "y": 5.2 }, { "x": 5.2, @@ -344,7 +344,7 @@ }, { "width": 150, - "height": 294 + "height": 292.4 }, { "width": 150, diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json index 115483cf4013..015df8fd02fb 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json @@ -84,8 +84,8 @@ "y": 5.2 }, { - "x": 5.6, - "y": 6.4 + "x": 5.2, + "y": 5.2 }, { "x": 5.2, @@ -259,7 +259,7 @@ }, { "width": 150, - "height": 290 + "height": 288.8 }, { "width": 150, @@ -279,34 +279,34 @@ }, { "width": 150, - "height": 223.6 + "height": 224 }, { "width": 150, - "height": 182.8 + "height": 183.2 }, { "width": 150, - "height": 141.2 + "height": 141.6 }, { "width": 150, - "height": 104 + "height": 104.4 }, { - "width": 147.6, - "height": 74 + "width": 148, + "height": 74.4 }, { "width": 139.6, - "height": 50.8 + "height": 51.2 }, { "width": 133.6, - "height": 32 + "height": 32.4 }, { - "width": 129.2, + "width": 129.6, "height": 15.6 }, { @@ -409,19 +409,19 @@ 1, 1, 1, - 0.99479187, - 0.8575029, - 0.65572864, - 0.4691311, - 0.3215357, - 0.21380007, - 0.13896108, - 0.0887118, - 0.05580789, - 0.03467691, - 0.021318138, - 0.0129826665, - 0.007839739, + 0.99532944, + 0.8594856, + 0.65783304, + 0.47089338, + 0.32286334, + 0.21474117, + 0.139602, + 0.089136004, + 0.056082606, + 0.03485179, + 0.02142787, + 0.013050735, + 0.007881463, 0, 0, 0, diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json index f44d4cd7c14e..5ac06217d161 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json @@ -72,7 +72,7 @@ }, { "x": 5.2, - "y": 5.6 + "y": 5.2 }, { "x": 5.2, @@ -306,11 +306,11 @@ }, { "width": 150, - "height": 293.2 + "height": 292.8 }, { "width": 150, - "height": 293.2 + "height": 292.8 }, { "width": 150, diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json index 9b68c71a7a34..1cae67b3eba7 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json @@ -79,8 +79,8 @@ "y": 5.2 }, { - "x": 5.6, - "y": 6.8 + "x": 5.2, + "y": 5.2 }, { "x": 5.2, @@ -334,7 +334,7 @@ }, { "width": 150, - "height": 294 + "height": 292.4 }, { "width": 150, diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json index 86805bd6ff29..ca87bc28c45d 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json @@ -83,8 +83,8 @@ "y": 5.2 }, { - "x": 5.6, - "y": 6.4 + "x": 5.2, + "y": 5.2 }, { "x": 5.2, @@ -254,7 +254,7 @@ }, { "width": 150, - "height": 290 + "height": 288.8 }, { "width": 150, @@ -274,27 +274,27 @@ }, { "width": 150, - "height": 223.6 + "height": 224 }, { "width": 150, - "height": 182.8 + "height": 183.2 }, { "width": 150, - "height": 141.2 + "height": 141.6 }, { "width": 150, - "height": 104 + "height": 104.4 }, { "width": 150, - "height": 72 + "height": 72.4 }, { "width": 150, - "height": 46 + "height": 46.4 }, { "width": 150, @@ -400,19 +400,19 @@ 1, 1, 1, - 0.99479187, - 0.8575029, - 0.65572864, - 0.4691311, - 0.3215357, - 0.21380007, - 0.13896108, - 0.0887118, - 0.05580789, - 0.03467691, - 0.021318138, - 0.0129826665, - 0.007839739, + 0.99532944, + 0.8594856, + 0.65783304, + 0.47089338, + 0.32286334, + 0.21474117, + 0.139602, + 0.089136004, + 0.056082606, + 0.03485179, + 0.02142787, + 0.013050735, + 0.007881463, 0, 0, 0, diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt index e4a87ba3a26b..7d9a32b3948a 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt @@ -58,17 +58,24 @@ class ElementStateAccessTest { assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(1f, 1f)) } + at(16) { + val semanticNode = onElement(TestElements.Foo).fetchSemanticsNode() + assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0.75f) + assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0.75f, 0.75f)) + } + at(32) { val semanticNode = onElement(TestElements.Foo).fetchSemanticsNode() assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0.5f) assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0.5f, 0.5f)) } - at(64) { + at(48) { val semanticNode = onElement(TestElements.Foo).fetchSemanticsNode() - assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0f) - assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0f, 0f)) + assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0.25f) + assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0.25f, 0.25f)) } + after { onElement(TestElements.Foo).assertDoesNotExist() } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt index 6c7783ae44e1..28b9e733be94 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.display.data.repository import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl import com.android.systemui.SysuiTestCase import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.testScope diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java index 0f631509bfba..d9990ba8e9ae 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java @@ -275,14 +275,14 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ "abc/.def", /* validateActivity */ true, /* enableSetting */true, /* enableOnLockScreen */ true); - mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0", + mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0, UserHandle.USER_CURRENT); - mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0", + mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0, UserHandle.USER_CURRENT); - mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1", + mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 1, UserHandle.USER_CURRENT); - mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1", + mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 1, UserHandle.USER_CURRENT); // Once from setup + twice from this function verify(mCallback, times(3)).onQRCodeScannerPreferenceChanged(); @@ -297,14 +297,14 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); - mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0", + mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0, UserHandle.USER_CURRENT); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isAllowedOnLockScreen()).isTrue(); assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); - mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1", + mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 1, UserHandle.USER_CURRENT); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt index a13b8647e170..101874807474 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt @@ -24,6 +24,7 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -126,7 +127,7 @@ class BundleEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isBubble() { - assertThat(underTest.isBubbleCapable).isFalse() + assertThat(underTest.isBubble).isFalse() } @Test @@ -152,4 +153,10 @@ class BundleEntryAdapterTest : SysuiTestCase() { fun canShowFullScreen() { assertThat(underTest.isFullScreenCapable()).isFalse() } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getPeopleNotificationType() { + assertThat(underTest.getPeopleNotificationType()).isEqualTo(TYPE_NON_PERSON) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt index 1018ded72783..7449064bc65b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.res.R import com.android.systemui.statusbar.RankingBuilder import com.android.systemui.statusbar.notification.mockNotificationActivityStarter +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.entryAdapterFactory import com.android.systemui.statusbar.notification.row.mockNotificationActionClickManager @@ -109,7 +110,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getRow_adapter() { - val row = Mockito.mock(ExpandableNotificationRow::class.java) + val row = mock(ExpandableNotificationRow::class.java) val notification: Notification = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() @@ -127,7 +128,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isGroupRoot_adapter_groupSummary() { - val row = Mockito.mock(ExpandableNotificationRow::class.java) + val row = mock(ExpandableNotificationRow::class.java) val notification: Notification = Notification.Builder(mContext, "") .setSmallIcon(R.drawable.ic_person) @@ -174,7 +175,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isClearable_adapter() { - val row = Mockito.mock(ExpandableNotificationRow::class.java) + val row = mock(ExpandableNotificationRow::class.java) val notification: Notification = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() @@ -192,7 +193,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getSummarization_adapter() { - val row = Mockito.mock(ExpandableNotificationRow::class.java) + val row = mock(ExpandableNotificationRow::class.java) val notification: Notification = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() @@ -212,7 +213,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getIcons_adapter() { - val row = Mockito.mock(ExpandableNotificationRow::class.java) + val row = mock(ExpandableNotificationRow::class.java) val notification: Notification = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() @@ -257,7 +258,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun canDragAndDrop() { - val pi = Mockito.mock(PendingIntent::class.java) + val pi = mock(PendingIntent::class.java) Mockito.`when`(pi.isActivity).thenReturn(true) val notification: Notification = Notification.Builder(mContext, "") @@ -283,7 +284,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { val entry = NotificationEntryBuilder().setNotification(notification).build() underTest = factory.create(entry) as NotificationEntryAdapter - assertThat(underTest.isBubbleCapable).isEqualTo(entry.isBubble) + assertThat(underTest.isBubble).isEqualTo(entry.isBubble) } @Test @@ -335,11 +336,21 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getPeopleNotificationType() { + val entry = kosmos.msgStyleBubbleableFullPerson + + underTest = factory.create(entry) as NotificationEntryAdapter + + assertThat(underTest.peopleNotificationType).isEqualTo(TYPE_FULL_PERSON) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) fun canShowFullScreen() { val notification: Notification = Notification.Builder(mContext, "") .setSmallIcon(R.drawable.ic_person) - .setFullScreenIntent(Mockito.mock(PendingIntent::class.java), true) + .setFullScreenIntent(mock(PendingIntent::class.java), true) .build() val entry = @@ -354,6 +365,22 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun onDragSuccess() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .addAction(mock(Notification.Action::class.java)) + .build() + val entry = NotificationEntryBuilder().setNotification(notification).build() + + underTest = factory.create(entry) as NotificationEntryAdapter + + underTest.onDragSuccess() + verify(kosmos.mockNotificationActivityStarter).onDragSuccess(entry) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) fun onNotificationBubbleIconClicked() { val notification: Notification = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() @@ -372,7 +399,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { val notification: Notification = Notification.Builder(mContext, "") .setSmallIcon(R.drawable.ic_person) - .addAction(Mockito.mock(Notification.Action::class.java)) + .addAction(mock(Notification.Action::class.java)) .build() val entry = NotificationEntryBuilder().setNotification(notification).build() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt index 7fa157fa7cb3..ba2d40ba6a0d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt @@ -27,7 +27,6 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest @@ -35,10 +34,10 @@ import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.core.StatusBarRootModernization -import com.android.systemui.statusbar.notification.buildNotificationEntry -import com.android.systemui.statusbar.notification.buildOngoingCallEntry -import com.android.systemui.statusbar.notification.buildPromotedOngoingEntry import com.android.systemui.statusbar.notification.collection.buildEntry +import com.android.systemui.statusbar.notification.collection.buildNotificationEntry +import com.android.systemui.statusbar.notification.collection.buildOngoingCallEntry +import com.android.systemui.statusbar.notification.collection.buildPromotedOngoingEntry import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.notifPipeline @@ -49,7 +48,6 @@ import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernizat import com.android.systemui.testKosmos import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runTest import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt index 893c17998a17..4c099b305332 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt @@ -455,6 +455,7 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() { assertThat(content).isNotNull() assertThat(content?.style).isEqualTo(Style.Call) + assertThat(content?.title).isEqualTo(TEST_PERSON_NAME) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt index 6926677feda0..6192399c522b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt @@ -28,9 +28,9 @@ import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.core.StatusBarRootModernization -import com.android.systemui.statusbar.notification.buildNotificationEntry -import com.android.systemui.statusbar.notification.buildOngoingCallEntry -import com.android.systemui.statusbar.notification.buildPromotedOngoingEntry +import com.android.systemui.statusbar.notification.collection.buildNotificationEntry +import com.android.systemui.statusbar.notification.collection.buildOngoingCallEntry +import com.android.systemui.statusbar.notification.collection.buildPromotedOngoingEntry import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt index d306a5bea433..0d453352e882 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt @@ -51,6 +51,7 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger import com.android.systemui.statusbar.notification.stack.NotificationListContainer @@ -318,7 +319,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { val notification = Notification.Builder(mContext).build() val sbn = SbnBuilder().setNotification(notification).setUser(UserHandle.of(USER_ALL)).build() - whenever(view.entry) + whenever(view.entryLegacy) .thenReturn( NotificationEntryBuilder().setSbn(sbn).setUser(UserHandle.of(USER_ALL)).build() ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java index 9fdfca14a5b2..ce120c51db6a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java @@ -38,7 +38,9 @@ import android.view.ViewGroup; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; @@ -54,6 +56,7 @@ import org.mockito.Mockito; @SmallTest public class NotificationMenuRowTest extends LeakCheckedTest { + private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); private ExpandableNotificationRow mRow; private View mView; private PeopleNotificationIdentifier mPeopleNotificationIdentifier; @@ -66,6 +69,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class); NotificationEntry entry = new NotificationEntryBuilder().build(); when(mRow.getEntry()).thenReturn(entry); + EntryAdapter entryAdapter = mKosmos.getEntryAdapterFactory().create(entry); + when(mRow.getEntryAdapter()).thenReturn(entryAdapter); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index 41cca19346f0..6ec1f919fb47 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -129,7 +129,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { whenever(notificationShelf.viewState).thenReturn(ExpandableViewState()) whenever(notificationRow.key).thenReturn("key") whenever(notificationRow.viewState).thenReturn(ExpandableViewState()) - whenever(notificationRow.entry).thenReturn(notificationEntry) + whenever(notificationRow.entryLegacy).thenReturn(notificationEntry) whenever(notificationRow.entryAdapter).thenReturn(notificationEntryAdapter) whenever(notificationRow.roundableState) .thenReturn(RoundableState(notificationRow, notificationRow, 0f)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt index 46430afecbb1..1f37291efbab 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt @@ -790,6 +790,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { } @Test + @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2) fun animateToGlanceableHub_affectsAlpha() = testScope.runTest { try { @@ -809,6 +810,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { } @Test + @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2) fun animateToGlanceableHub_alphaResetOnCommunalNotShowing() = testScope.runTest { try { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java index c23e0e733b41..1cc291199531 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java @@ -221,6 +221,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { when(enr.getPrivateLayout()).thenReturn(privateLayout); when(enr.getEntry()).thenReturn(enrEntry); + when(enr.getEntryLegacy()).thenReturn(enrEntry); when(enr.getEntryAdapter()).thenReturn(enrEntryAdapter); when(enr.isChildInGroup()).thenReturn(true); when(enr.areChildrenExpanded()).thenReturn(false); @@ -251,6 +252,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { when(enr.getPrivateLayout()).thenReturn(privateLayout); when(enr.getEntry()).thenReturn(enrEntry); + when(enr.getEntryLegacy()).thenReturn(enrEntry); when(enr.isChildInGroup()).thenReturn(true); when(enr.areChildrenExpanded()).thenReturn(true); @@ -277,6 +279,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { when(enr.getPrivateLayout()).thenReturn(privateLayout); when(enr.getEntry()).thenReturn(enrEntry); + when(enr.getEntryLegacy()).thenReturn(enrEntry); when(enr.isChildInGroup()).thenReturn(false); when(enr.isPinned()).thenReturn(false); when(enr.isExpanded()).thenReturn(false); @@ -305,6 +308,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { when(enr.getPrivateLayout()).thenReturn(privateLayout); when(enr.getEntry()).thenReturn(enrEntry); + when(enr.getEntryLegacy()).thenReturn(enrEntry); when(enr.isChildInGroup()).thenReturn(false); when(enr.isPinned()).thenReturn(false); when(enr.isExpanded()).thenReturn(true); @@ -333,6 +337,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { when(enr.getPrivateLayout()).thenReturn(privateLayout); when(enr.getEntry()).thenReturn(enrEntry); + when(enr.getEntryLegacy()).thenReturn(enrEntry); when(enr.isChildInGroup()).thenReturn(false); when(enr.isPinned()).thenReturn(true); when(enr.isPinnedAndExpanded()).thenReturn(false); @@ -361,6 +366,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { when(enr.getPrivateLayout()).thenReturn(privateLayout); when(enr.getEntry()).thenReturn(enrEntry); + when(enr.getEntryLegacy()).thenReturn(enrEntry); when(enr.isChildInGroup()).thenReturn(false); when(enr.isPinned()).thenReturn(true); when(enr.isPinnedAndExpanded()).thenReturn(true); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index bafa8cf05a7f..da5622a2a8da 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -17,8 +17,10 @@ package com.android.systemui.user.data.repository +import android.app.admin.DevicePolicyManager import android.app.admin.devicePolicyManager import android.content.Intent +import android.content.applicationContext import android.content.pm.UserInfo import android.internal.statusbar.fakeStatusBarService import android.os.UserHandle @@ -77,10 +79,7 @@ class UserRepositoryImplTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) tracker = FakeUserTracker() - context.orCreateTestableResources.addOverride( - R.bool.config_userSwitchingMustGoThroughLoginScreen, - false, - ) + setUserSwitchingMustGoThroughLoginScreen(false) } @Test @@ -308,6 +307,117 @@ class UserRepositoryImplTest : SysuiTestCase() { job.cancel() } + @Test + fun isSecondaryUserLogoutEnabled_secondaryLogoutDisabled_alwaysFalse() = + testScope.runTest { + underTest = create(testScope.backgroundScope) + mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT) + setSecondaryUserLogoutEnabled(false) + setUpUsers(count = 2, selectedIndex = 0) + tracker.onProfileChanged() + + val secondaryUserLogoutEnabled by + collectLastValue(underTest.isSecondaryUserLogoutEnabled) + + assertThat(secondaryUserLogoutEnabled).isFalse() + + setUpUsers(count = 2, selectedIndex = 1) + tracker.onProfileChanged() + assertThat(secondaryUserLogoutEnabled).isFalse() + } + + @Test + fun isSecondaryUserLogoutEnabled_secondaryLogoutEnabled_NullLogoutUser_alwaysFalse() = + testScope.runTest { + underTest = create(testScope.backgroundScope) + mockLogoutUser(LogoutUserResult.NONE) + setSecondaryUserLogoutEnabled(true) + setUpUsers(count = 2, selectedIndex = 0) + tracker.onProfileChanged() + + val secondaryUserLogoutEnabled by + collectLastValue(underTest.isSecondaryUserLogoutEnabled) + + assertThat(secondaryUserLogoutEnabled).isFalse() + + setUpUsers(count = 2, selectedIndex = 1) + tracker.onProfileChanged() + assertThat(secondaryUserLogoutEnabled).isFalse() + } + + @Test + fun isSecondaryUserLogoutEnabled_secondaryLogoutEnabled_NonSystemLogoutUser_trueWhenNonSystem() = + testScope.runTest { + underTest = create(testScope.backgroundScope) + mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT) + setSecondaryUserLogoutEnabled(true) + setUpUsers(count = 2, selectedIndex = 0) + tracker.onProfileChanged() + + val secondaryUserLogoutEnabled by + collectLastValue(underTest.isSecondaryUserLogoutEnabled) + + assertThat(secondaryUserLogoutEnabled).isFalse() + + setUpUsers(count = 2, selectedIndex = 1) + tracker.onProfileChanged() + assertThat(secondaryUserLogoutEnabled).isTrue() + } + + @Test + fun isLogoutToSystemUserEnabled_logoutThroughLoginScreenDisabled_alwaysFalse() = + testScope.runTest { + underTest = create(testScope.backgroundScope) + mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT) + setUserSwitchingMustGoThroughLoginScreen(false) + setUpUsers(count = 2, selectedIndex = 0) + tracker.onProfileChanged() + + val logoutToSystemUserEnabled by collectLastValue(underTest.isLogoutToSystemUserEnabled) + + assertThat(logoutToSystemUserEnabled).isFalse() + + setUpUsers(count = 2, selectedIndex = 1) + tracker.onProfileChanged() + assertThat(logoutToSystemUserEnabled).isFalse() + } + + @Test + fun isLogoutToSystemUserEnabled_logoutThroughLoginScreenEnabled_NullLogoutUser_alwaysFalse() = + testScope.runTest { + underTest = create(testScope.backgroundScope) + mockLogoutUser(LogoutUserResult.NONE) + setUserSwitchingMustGoThroughLoginScreen(true) + setUpUsers(count = 2, selectedIndex = 0) + tracker.onProfileChanged() + + val logoutToSystemUserEnabled by collectLastValue(underTest.isLogoutToSystemUserEnabled) + + assertThat(logoutToSystemUserEnabled).isFalse() + + setUpUsers(count = 2, selectedIndex = 1) + tracker.onProfileChanged() + assertThat(logoutToSystemUserEnabled).isFalse() + } + + @Test + fun isLogoutToSystemUserEnabled_logoutThroughLoginScreenEnabled_NonSystemLogoutUser_trueWhenNonSystem() = + testScope.runTest { + underTest = create(testScope.backgroundScope) + mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT) + setUserSwitchingMustGoThroughLoginScreen(true) + setUpUsers(count = 2, selectedIndex = 0) + tracker.onProfileChanged() + + val logoutToSystemUserEnabled by collectLastValue(underTest.isLogoutToSystemUserEnabled) + + assertThat(logoutToSystemUserEnabled).isFalse() + + setUpUsers(count = 2, selectedIndex = 1) + tracker.onProfileChanged() + assertThat(logoutToSystemUserEnabled).isTrue() + } + private fun createUserInfo(id: Int, isGuest: Boolean): UserInfo { val flags = 0 return UserInfo( @@ -354,6 +464,38 @@ class UserRepositoryImplTest : SysuiTestCase() { assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled) } + private fun setSecondaryUserLogoutEnabled(enabled: Boolean) { + whenever(devicePolicyManager.isLogoutEnabled).thenReturn(enabled) + broadcastDispatcher.sendIntentToMatchingReceiversOnly( + kosmos.applicationContext, + Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), + ) + } + + private fun setUserSwitchingMustGoThroughLoginScreen(enabled: Boolean) { + context.orCreateTestableResources.addOverride( + R.bool.config_userSwitchingMustGoThroughLoginScreen, + enabled, + ) + } + + private fun mockLogoutUser(result: LogoutUserResult) { + when (result) { + LogoutUserResult.NONE -> { + whenever(devicePolicyManager.logoutUser).thenReturn(null) + } + LogoutUserResult.NON_SYSTEM_CURRENT -> { + whenever(devicePolicyManager.logoutUser).thenAnswer { + if (tracker.userHandle != UserHandle.SYSTEM) { + tracker.userHandle + } else { + null + } + } + } + } + } + private fun create(scope: CoroutineScope): UserRepositoryImpl { return UserRepositoryImpl( appContext = context, @@ -373,4 +515,9 @@ class UserRepositoryImplTest : SysuiTestCase() { companion object { @JvmStatic private val IMMEDIATE = Dispatchers.Main.immediate } + + private enum class LogoutUserResult { + NONE, + NON_SYSTEM_CURRENT, + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt index 04ab98889755..b1a3caf98f09 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel import android.bluetooth.BluetoothDevice +import android.graphics.drawable.TestStubDrawable import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.bluetooth.CachedBluetoothDevice @@ -63,6 +64,12 @@ class AudioSharingStreamSliderViewModelTest : SysuiTestCase() { assertThat(audioSharingSlider!!.label).isEqualTo("my headset 2") assertThat(audioSharingSlider!!.icon) - .isEqualTo(Icon.Resource(R.drawable.ic_volume_media_bt, null)) + .isEqualTo( + Icon.Loaded( + drawable = TestStubDrawable(), + res = R.drawable.ic_volume_media_bt, + contentDescription = null, + ) + ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt index 9e8cde3bc936..ffe8e923815f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel import android.app.Flags import android.app.NotificationManager.INTERRUPTION_FILTER_NONE +import android.graphics.drawable.TestStubDrawable import android.media.AudioManager import android.platform.test.annotations.EnableFlags import android.service.notification.ZenPolicy @@ -28,7 +29,6 @@ import com.android.settingslib.notification.modes.TestModeBuilder import com.android.settingslib.volume.shared.model.AudioStream import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.collectLastValue @@ -39,8 +39,6 @@ import com.android.systemui.statusbar.policy.data.repository.fakeZenModeReposito import com.android.systemui.testKosmos import com.android.systemui.volume.data.repository.audioSharingRepository import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock @@ -173,6 +171,12 @@ class AudioStreamSliderViewModelTest : SysuiTestCase() { assertThat(mediaSlider!!.label).isEqualTo("my headset 1") assertThat(mediaSlider!!.icon) - .isEqualTo(Icon.Resource(R.drawable.ic_volume_media_bt, null)) + .isEqualTo( + Icon.Loaded( + drawable = TestStubDrawable(), + res = R.drawable.ic_volume_media_bt, + contentDescription = null, + ) + ) } } diff --git a/packages/SystemUI/res/color/brightness_slider_track.xml b/packages/SystemUI/res/color/brightness_slider_track.xml new file mode 100644 index 000000000000..c6e9b65fb7e8 --- /dev/null +++ b/packages/SystemUI/res/color/brightness_slider_track.xml @@ -0,0 +1,19 @@ +<?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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral2_500" android:lStar="40" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml index 88d3ecb3f423..d38da7b766c1 100644 --- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml +++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml @@ -25,7 +25,7 @@ <shape> <size android:height="@dimen/rounded_slider_track_width" /> <corners android:radius="@dimen/rounded_slider_track_corner_radius" /> - <solid android:color="@androidprv:color/customColorShadeInactive" /> + <solid android:color="@color/brightness_slider_track" /> </shape> </inset> </item> diff --git a/packages/SystemUI/res/drawable/hearing_device_ambient_expand_icon_background.xml b/packages/SystemUI/res/drawable/hearing_device_ambient_expand_icon_background.xml new file mode 100644 index 000000000000..2173641376fa --- /dev/null +++ b/packages/SystemUI/res/drawable/hearing_device_ambient_expand_icon_background.xml @@ -0,0 +1,21 @@ +<?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. +--> + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/hearing_device_ambient_icon_background" + android:insetTop="10dp" + android:insetBottom="10dp"/> diff --git a/packages/SystemUI/res/drawable/hearing_device_ambient_icon_background.xml b/packages/SystemUI/res/drawable/hearing_device_ambient_icon_background.xml new file mode 100644 index 000000000000..81ef3d7c11b7 --- /dev/null +++ b/packages/SystemUI/res/drawable/hearing_device_ambient_icon_background.xml @@ -0,0 +1,27 @@ +<?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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <solid android:color="@androidprv:color/materialColorSurfaceContainerHighest" /> + <corners + android:bottomLeftRadius="?android:attr/dialogCornerRadius" + android:topLeftRadius="?android:attr/dialogCornerRadius" + android:bottomRightRadius="?android:attr/dialogCornerRadius" + android:topRightRadius="?android:attr/dialogCornerRadius" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml b/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml index 73704f823033..f17cc96329c6 100644 --- a/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml +++ b/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml @@ -21,7 +21,7 @@ android:height="2dp" android:width="@dimen/rear_display_progress_width"> <shape android:shape="rectangle"> - <solid android:color="@androidprv:color/materialColorSurfaceContainer" /> + <solid android:color="?android:attr/colorAccent"/> </shape> </item> <item @@ -29,4 +29,4 @@ android:gravity="center_vertical|fill_horizontal"> <com.android.systemui.util.RoundedCornerProgressDrawable android:drawable="@drawable/rear_display_dialog_seekbar_progress" /> </item> -</layer-list>
\ No newline at end of file +</layer-list> diff --git a/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml b/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml index fd409a5a8bb1..d3e9db15dfdd 100644 --- a/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml +++ b/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml @@ -36,7 +36,8 @@ android:padding="12dp" android:contentDescription="@string/hearing_devices_ambient_unmute" android:src="@drawable/ic_ambient_volume" - android:tint="@androidprv:color/materialColorOnSurface" /> + android:tint="@androidprv:color/materialColorOnSurface" + android:background="@drawable/hearing_device_ambient_icon_background"/> <TextView android:id="@+id/ambient_title" android:layout_width="0dp" @@ -56,7 +57,8 @@ android:padding="10dp" android:contentDescription="@string/hearing_devices_ambient_expand_controls" android:src="@drawable/ic_hearing_device_expand" - android:tint="@androidprv:color/materialColorOnSurface" /> + android:tint="@androidprv:color/materialColorOnSurface" + android:background="@drawable/hearing_device_ambient_expand_icon_background"/> </LinearLayout> <LinearLayout android:id="@+id/ambient_control_container" diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 15519ff37d0c..7c6a1b1bf63d 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -146,7 +146,8 @@ <color name="smart_reply_button_stroke">@*android:color/accent_device_default</color> <!-- Magic Action colors --> - <color name="magic_action_button_text_color">@androidprv:color/materialColorOnSurfaceVariant</color> + <color name="magic_action_button_text_color">@androidprv:color/materialColorOnSurface</color> + <color name="magic_action_button_stroke_color">@androidprv:color/materialColorOnSurface</color> <!-- Biometric dialog colors --> <color name="biometric_dialog_gray">#ff757575</color> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3fdb98b4e00b..b627bdf22a6c 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1483,6 +1483,8 @@ <string name="media_projection_entry_app_permission_dialog_continue_entire_screen">Share screen</string> <!-- 1P/3P apps disabled the single app projection option. [CHAR LIMIT=NONE] --> <string name="media_projection_entry_app_permission_dialog_single_app_disabled"><xliff:g id="app_name" example="Meet">%1$s</xliff:g> has disabled this option</string> + <!-- Explanation that the app requesting the projection does not support single app sharing[CHAR LIMIT=70] --> + <string name="media_projection_entry_app_permission_dialog_single_app_not_supported">Not supported by the app</string> <!-- Title of the activity that allows users to select an app to share to a 1P/3P app [CHAR LIMIT=70] --> <string name="media_projection_entry_share_app_selector_title">Choose app to share</string> @@ -2558,6 +2560,9 @@ <!-- Button to edit the tile ordering of quick settings [CHAR LIMIT=60] --> <string name="qs_edit">Edit</string> + <!-- Title for QS Edit mode screen [CHAR LIMIT=30] --> + <string name="qs_edit_tiles">Edit tiles</string> + <!-- SysUI Tuner: Options for how clock is displayed [CHAR LIMIT=NONE] --> <string name="tuner_time">Time</string> diff --git a/packages/SystemUI/shared/res/values/bools.xml b/packages/SystemUI/shared/res/values/bools.xml index 98e5cea0e78f..a7ad48d17abb 100644 --- a/packages/SystemUI/shared/res/values/bools.xml +++ b/packages/SystemUI/shared/res/values/bools.xml @@ -22,7 +22,4 @@ <resources> <!-- Whether to add padding at the bottom of the complication clock --> <bool name="dream_overlay_complication_clock_bottom_padding">false</bool> - - <!-- Whether to mark tasks that are present in the UI as perceptible tasks. --> - <bool name="config_usePerceptibleTasks">false</bool> </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index 487d1ce2514e..b981f9837787 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -46,8 +46,6 @@ import android.view.Display; import android.window.TaskSnapshot; import com.android.internal.app.IVoiceInteractionManagerService; -import com.android.server.am.Flags; -import com.android.systemui.shared.R; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -324,14 +322,6 @@ public class ActivityManagerWrapper { } /** - * Returns true if tasks with a presence in the UI should be marked as perceptible tasks. - */ - public static boolean usePerceptibleTasks(Context context) { - return Flags.perceptibleTasks() - && context.getResources().getBoolean(R.bool.config_usePerceptibleTasks); - } - - /** * Returns true if the running task represents the home task */ public static boolean isHomeTask(RunningTaskInfo info) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java index bd3dfe049587..8025d4b0c2ea 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java @@ -317,14 +317,14 @@ class MenuViewAppearance { public static float avoidVerticalDisplayCutout( float y, float menuHeight, Rect bounds, Rect cutout) { if (cutout.top > y + menuHeight || cutout.bottom < y) { - return y; + return clampVerticalPosition(y, menuHeight, bounds.top, bounds.bottom); } boolean topAvailable = cutout.top - bounds.top >= menuHeight; boolean bottomAvailable = bounds.bottom - cutout.bottom >= menuHeight; boolean topOrBottom; if (!topAvailable && !bottomAvailable) { - return y; + return clampVerticalPosition(y, menuHeight, bounds.top, bounds.bottom); } else if (topAvailable && !bottomAvailable) { topOrBottom = true; } else if (!topAvailable && bottomAvailable) { @@ -332,7 +332,16 @@ class MenuViewAppearance { } else { topOrBottom = y + menuHeight * 0.5f < cutout.centerY(); } - return (topOrBottom) ? cutout.top - menuHeight : cutout.bottom; + + float finalPosition = (topOrBottom) ? cutout.top - menuHeight : cutout.bottom; + return clampVerticalPosition(finalPosition, menuHeight, bounds.top, bounds.bottom); + } + + private static float clampVerticalPosition( + float position, float height, float min, float max) { + position = Float.max(min + height / 2, position); + position = Float.min(max - height / 2, position); + return position; } boolean isMenuOnLeftSide() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 22d2aaf2a8e7..87e9784ddbc9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -32,7 +32,6 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.logging.KeyguardLogger import com.android.settingslib.Utils import com.android.systemui.CoreStartable -import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.dagger.SysUISingleton @@ -43,6 +42,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R +import com.android.systemui.shared.Flags.ambientAod import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LiftReveal import com.android.systemui.statusbar.LightRevealEffect @@ -196,7 +196,7 @@ constructor( // This code path is not used if the KeyguardTransitionRepository is managing the light // reveal scrim. - if (!lightRevealMigration()) { + if (!ambientAod()) { if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) { circleReveal?.let { lightRevealScrim.revealAmount = 0f @@ -213,7 +213,7 @@ constructor( } override fun onKeyguardFadingAwayChanged() { - if (lightRevealMigration()) { + if (ambientAod()) { return } diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt index 9db7b50905f8..1301fb87069e 100644 --- a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt @@ -17,9 +17,9 @@ package com.android.systemui.common.domain.interactor import android.util.Log +import com.android.app.displaylib.PerDisplayRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.display.data.repository.DisplayRepository -import com.android.systemui.display.data.repository.PerDisplayRepository import com.android.systemui.model.StateChange import com.android.systemui.model.SysUiState import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt index 2adaec21867f..6792f3188986 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt @@ -19,6 +19,7 @@ package com.android.systemui.common.shared.model import android.annotation.DrawableRes import android.graphics.drawable.Drawable import androidx.compose.runtime.Stable +import com.android.systemui.common.shared.model.Icon.Loaded /** * Models an icon, that can either be already [loaded][Icon.Loaded] or be a [reference] @@ -33,8 +34,37 @@ sealed class Icon { constructor( val drawable: Drawable, override val contentDescription: ContentDescription?, + /** + * Serves as an id to compare two instances. When provided this is used alongside + * [contentDescription] to determine equality. This is useful when comparing icons + * representing the same UI, but with different [drawable] instances. + */ @DrawableRes val res: Int? = null, - ) : Icon() + ) : Icon() { + + override fun equals(other: Any?): Boolean { + val that = other as? Loaded ?: return false + + if (this.res != null && that.res != null) { + return this.res == that.res && this.contentDescription == that.contentDescription + } + + return this.res == that.res && + this.drawable == that.drawable && + this.contentDescription == that.contentDescription + } + + override fun hashCode(): Int { + var result = contentDescription?.hashCode() ?: 0 + result = + if (res != null) { + 31 * result + res.hashCode() + } else { + 31 * result + drawable.hashCode() + } + return result + } + } data class Resource( @DrawableRes val res: Int, @@ -49,4 +79,4 @@ sealed class Icon { fun Drawable.asIcon( contentDescription: ContentDescription? = null, @DrawableRes res: Int? = null, -): Icon.Loaded = Icon.Loaded(this, contentDescription, res) +): Loaded = Loaded(this, contentDescription, res) diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 756edb3d048d..5a4b0b0e2d24 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -229,7 +229,7 @@ constructor( MutableStateFlow(false) } - val blurRadiusPx: Float = blurConfig.maxBlurRadiusPx / 2.0f + val blurRadiusPx: Float = blurConfig.maxBlurRadiusPx init { // Initialize our media host for the UMO. This only needs to happen once and must be done diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt index 39708a743c23..3520439fda88 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt @@ -16,9 +16,9 @@ package com.android.systemui.dagger -import com.android.systemui.display.data.repository.DefaultDisplayOnlyInstanceRepositoryImpl -import com.android.systemui.display.data.repository.PerDisplayInstanceRepositoryImpl -import com.android.systemui.display.data.repository.PerDisplayRepository +import com.android.app.displaylib.DefaultDisplayOnlyInstanceRepositoryImpl +import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl +import com.android.app.displaylib.PerDisplayRepository import com.android.systemui.model.SysUIStateInstanceProvider import com.android.systemui.model.SysUiState import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index c201fbf0051b..edee64e50b53 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -65,7 +65,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.demomode.dagger.DemoModeModule; import com.android.systemui.deviceentry.DeviceEntryModule; import com.android.systemui.display.DisplayModule; -import com.android.systemui.display.data.repository.PerDisplayRepository; +import com.android.app.displaylib.PerDisplayRepository; import com.android.systemui.doze.dagger.DozeComponent; import com.android.systemui.dreams.dagger.DreamModule; import com.android.systemui.flags.FeatureFlags; diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt index f3316958f01d..908d0aafb2b9 100644 --- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt +++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt @@ -18,7 +18,9 @@ package com.android.systemui.display import android.hardware.display.DisplayManager import android.os.Handler +import com.android.app.displaylib.DisplayLibBackground import com.android.app.displaylib.DisplayLibComponent +import com.android.app.displaylib.PerDisplayRepository import com.android.app.displaylib.createDisplayLibComponent import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton @@ -34,7 +36,6 @@ import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepos import com.android.systemui.display.data.repository.FocusedDisplayRepository import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl import com.android.systemui.display.data.repository.PerDisplayRepoDumpHelper -import com.android.systemui.display.data.repository.PerDisplayRepository import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl import com.android.systemui.display.domain.interactor.DisplayWindowPropertiesInteractorModule @@ -85,6 +86,10 @@ interface DisplayModule { @Binds fun dumpRegistrationLambda(helper: PerDisplayRepoDumpHelper): PerDisplayRepository.InitCallback + @Binds + @DisplayLibBackground + fun bindDisplayLibBackground(@Background bgScope: CoroutineScope): CoroutineScope + companion object { @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt index a56710ee3772..86c9d84c27b1 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt @@ -16,6 +16,9 @@ package com.android.systemui.display.data.repository +import com.android.app.displaylib.PerDisplayRepository + +// TODO b/401305290 - move to displaylib class FakePerDisplayRepository<T> : PerDisplayRepository<T> { private val instances = mutableMapOf<Int, T>() diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt index 212d55612935..efbae5d04caf 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt @@ -16,6 +16,7 @@ package com.android.systemui.display.data.repository +import com.android.app.displaylib.PerDisplayRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.dump.DumpableFromToString diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt deleted file mode 100644 index 7e00c60dc43a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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.display.data.repository - -import android.util.Log -import android.view.Display -import com.android.app.tracing.coroutines.launchTraced as launch -import com.android.app.tracing.traceSection -import com.android.systemui.dagger.qualifiers.Background -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import java.util.concurrent.ConcurrentHashMap -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collectLatest - -/** - * Used to create instances of type `T` for a specific display. - * - * This is useful for resources or objects that need to be managed independently for each connected - * display (e.g., UI state, rendering contexts, or display-specific configurations). - * - * Note that in most cases this can be implemented by a simple `@AssistedFactory` with `displayId` - * parameter - * - * ```kotlin - * class SomeType @AssistedInject constructor(@Assisted displayId: Int,..) - * @AssistedFactory - * interface Factory { - * fun create(displayId: Int): SomeType - * } - * } - * ``` - * - * Then it can be used to create a [PerDisplayRepository] as follows: - * ```kotlin - * // Injected: - * val repositoryFactory: PerDisplayRepositoryImpl.Factory - * val instanceFactory: PerDisplayRepositoryImpl.Factory - * // repository creation: - * repositoryFactory.create(instanceFactory::create) - * ``` - * - * @see PerDisplayRepository For how to retrieve and manage instances created by this factory. - */ -fun interface PerDisplayInstanceProvider<T> { - /** Creates an instance for a display. */ - fun createInstance(displayId: Int): T? -} - -/** - * Extends [PerDisplayInstanceProvider], adding support for destroying the instance. - * - * This is useful for releasing resources associated with a display when it is disconnected or when - * the per-display instance is no longer needed. - */ -interface PerDisplayInstanceProviderWithTeardown<T> : PerDisplayInstanceProvider<T> { - /** Destroys a previously created instance of `T` forever. */ - fun destroyInstance(instance: T) -} - -/** - * Provides access to per-display instances of type `T`. - * - * Acts as a repository, managing the caching and retrieval of instances created by a - * [PerDisplayInstanceProvider]. It ensures that only one instance of `T` exists per display ID. - */ -interface PerDisplayRepository<T> { - /** Gets the cached instance or create a new one for a given display. */ - operator fun get(displayId: Int): T? - - /** Debug name for this repository, mainly for tracing and logging. */ - val debugName: String - - /** - * Callback to run when a given repository is initialized. - * - * This allows the caller to perform custom logic when the repository is ready to be used, e.g. - * register to dumpManager. - * - * Note that the instance is *leaked* outside of this class, so it should only be done when - * repository is meant to live as long as the caller. In systemUI this is ok because the - * repository lives as long as the process itself. - */ - interface InitCallback { - fun onInit(debugName: String, instance: Any) - } -} - -/** - * Default implementation of [PerDisplayRepository]. - * - * This class manages a cache of per-display instances of type `T`, creating them using a provided - * [PerDisplayInstanceProvider] and optionally tearing them down using a - * [PerDisplayInstanceProviderWithTeardown] when displays are disconnected. - * - * It listens to the [DisplayRepository] to detect when displays are added or removed, and - * automatically manages the lifecycle of the per-display instances. - * - * Note that this is a [PerDisplayStoreImpl] 2.0 that doesn't require [CoreStartable] bindings, - * providing all args in the constructor. - */ -class PerDisplayInstanceRepositoryImpl<T> -@AssistedInject -constructor( - @Assisted override val debugName: String, - @Assisted private val instanceProvider: PerDisplayInstanceProvider<T>, - @Background private val backgroundApplicationScope: CoroutineScope, - private val displayRepository: DisplayRepository, - private val initCallback: PerDisplayRepository.InitCallback, -) : PerDisplayRepository<T> { - - private val perDisplayInstances = ConcurrentHashMap<Int, T?>() - - init { - backgroundApplicationScope.launch("$debugName#start") { start() } - } - - private suspend fun start() { - initCallback.onInit(debugName, this) - displayRepository.displayIds.collectLatest { displayIds -> - val toRemove = perDisplayInstances.keys - displayIds - toRemove.forEach { displayId -> - Log.d(TAG, "<$debugName> destroying instance for displayId=$displayId.") - perDisplayInstances.remove(displayId)?.let { instance -> - (instanceProvider as? PerDisplayInstanceProviderWithTeardown)?.destroyInstance( - instance - ) - } - } - } - } - - override fun get(displayId: Int): T? { - if (displayRepository.getDisplay(displayId) == null) { - Log.e(TAG, "<$debugName: Display with id $displayId doesn't exist.") - return null - } - - // If it doesn't exist, create it and put it in the map. - return perDisplayInstances.computeIfAbsent(displayId) { key -> - Log.d(TAG, "<$debugName> creating instance for displayId=$key, as it wasn't available.") - val instance = - traceSection({ "creating instance of $debugName for displayId=$key" }) { - instanceProvider.createInstance(key) - } - if (instance == null) { - Log.e( - TAG, - "<$debugName> returning null because createInstance($key) returned null.", - ) - } - instance - } - } - - @AssistedFactory - interface Factory<T> { - fun create( - debugName: String, - instanceProvider: PerDisplayInstanceProvider<T>, - ): PerDisplayInstanceRepositoryImpl<T> - } - - companion object { - private const val TAG = "PerDisplayInstanceRepo" - } - - override fun toString(): String { - return "PerDisplayInstanceRepositoryImpl(" + - "debugName='$debugName', instances=$perDisplayInstances)" - } -} - -/** - * Provides an instance of a given class **only** for the default display, even if asked for another - * display. - * - * This is useful in case of **flag refactors**: it can be provided instead of an instance of - * [PerDisplayInstanceRepositoryImpl] when a flag related to multi display refactoring is off. - * - * Note that this still requires all instances to be provided by a [PerDisplayInstanceProvider]. If - * you want to provide an existing instance instead for the default display, either implement it in - * a custom [PerDisplayInstanceProvider] (e.g. inject it in the constructor and return it if the - * displayId is zero), or use [SingleInstanceRepositoryImpl]. - */ -class DefaultDisplayOnlyInstanceRepositoryImpl<T>( - override val debugName: String, - private val instanceProvider: PerDisplayInstanceProvider<T>, -) : PerDisplayRepository<T> { - private val lazyDefaultDisplayInstance by lazy { - instanceProvider.createInstance(Display.DEFAULT_DISPLAY) - } - - override fun get(displayId: Int): T? = lazyDefaultDisplayInstance -} - -/** - * Always returns [instance] for any display. - * - * This can be used to provide a single instance based on a flag value during a refactor. Similar to - * [DefaultDisplayOnlyInstanceRepositoryImpl], but also avoids creating the - * [PerDisplayInstanceProvider]. This is useful when you want to provide an existing instance only, - * without even instantiating a [PerDisplayInstanceProvider]. - */ -class SingleInstanceRepositoryImpl<T>(override val debugName: String, private val instance: T) : - PerDisplayRepository<T> { - override fun get(displayId: Int): T? = instance -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index c61530c3dbcc..74cf7e4f7359 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -20,7 +20,6 @@ package com.android.systemui.keyguard import android.content.Context import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.CoreStartable -import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.biometrics.ui.binder.DeviceEntryUnlockTrackerViewBinder import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.SysUISingleton @@ -46,6 +45,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shared.Flags.ambientAod import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.VibratorHelper @@ -105,7 +105,7 @@ constructor( bindJankViewModel() initializeViews() - if (lightRevealMigration()) { + if (ambientAod()) { LightRevealScrimViewBinder.bind( lightRevealScrim, lightRevealScrimViewModel, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt index 0b116ded42da..438dff9cb476 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt @@ -139,7 +139,7 @@ constructor( fun setWallpaperSupportsAmbientMode(supportsAmbientMode: Boolean) { repository.maxAlpha.value = if (supportsAmbientMode) { - 0.7f + 0.54f } else { 1f } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt index 45f8f10595e4..3cf05062325d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils -import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.communal.ui.compose.TransitionDuration import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.dagger.GlanceableHubBlurComponent @@ -28,6 +27,7 @@ import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.keyguard.ui.transitions.GlanceableHubTransition import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shared.Flags.ambientAod import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -56,14 +56,14 @@ constructor( return transitionAnimation.sharedFlow( duration = 250.milliseconds, startTime = - if (lightRevealMigration()) { + if (ambientAod()) { 100.milliseconds // Wait for the light reveal to "hit" the LS elements. } else { 0.milliseconds }, onStart = { currentAlpha = - if (lightRevealMigration()) { + if (ambientAod()) { viewState.alpha() } else { 0f diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt index d981eeb0989b..ba6bda8a2a04 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils -import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -25,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.shared.Flags.ambientAod import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -52,13 +52,13 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTra return transitionAnimation.sharedFlow( duration = 250.milliseconds, startTime = - if (lightRevealMigration()) { + if (ambientAod()) { 100.milliseconds // Wait for the light reveal to "hit" the LS elements. } else { 0.milliseconds }, onStart = { - if (lightRevealMigration()) { + if (ambientAod()) { currentAlpha = viewState.alpha() } else { currentAlpha = 0f diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt index b15cacf077a4..2eb5bf9328e6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils -import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -25,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.shared.Flags.ambientAod import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -56,7 +56,7 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTra duration = 250.milliseconds, startTime = 0.milliseconds, onStart = { - if (lightRevealMigration()) { + if (ambientAod()) { currentAlpha = viewState.alpha() } else { currentAlpha = 0f diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt index c6e4db7af2d9..324a3efc2989 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt @@ -150,6 +150,13 @@ private class OptionsAdapter(context: Context, private val options: List<ScreenS titleTextView.isEnabled = true } else { errorTextView.visibility = View.VISIBLE + if (com.android.systemui.Flags.mediaProjectionGreyErrorText()) { + errorTextView.isEnabled = false + errorTextView.setTextColor(context.getColorStateList(R.color.menu_item_text)) + errorTextView.setText( + R.string.media_projection_entry_app_permission_dialog_single_app_not_supported + ) + } titleTextView.isEnabled = false } return view diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt index 71cb74543485..68cd80798c4b 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt @@ -17,9 +17,9 @@ package com.android.systemui.model import android.util.Log import android.view.Display +import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.display.data.repository.PerDisplayInstanceProviderWithTeardown import com.android.systemui.dump.DumpManager import com.android.systemui.model.SysUiState.SysUiStateCallback import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java index 58ddbf60e8fb..39482bea0111 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java @@ -24,10 +24,10 @@ import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; +import com.android.app.displaylib.PerDisplayRepository; import com.android.app.viewcapture.ViewCapture; import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.systemui.dagger.qualifiers.DisplayId; -import com.android.systemui.display.data.repository.PerDisplayRepository; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope; import com.android.systemui.navigationbar.views.NavigationBarFrame; diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index d20b360756d7..728652e6e5c4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -82,6 +82,7 @@ fun ContentScope.QuickQuickSettings( viewModel.tileHapticsViewModelFactoryProvider, // There should be no QuickQuickSettings when the details view is enabled. detailsViewModel = null, + isVisible = listening, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt index d40ecc9565ae..1176095fbb1c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.panels.ui.compose +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -23,11 +24,13 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -63,6 +66,7 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode val title = tileDetailedViewModel.title val subTitle = tileDetailedViewModel.subTitle + val colors = MaterialTheme.colorScheme Column( modifier = @@ -70,20 +74,33 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode .fillMaxWidth() // The height of the details view is TBD. .fillMaxHeight() + .background(color = colors.onPrimary) ) { CompositionLocalProvider( value = LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant ) { Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .padding( + start = TileDetailsDefaults.TitleRowStart, + top = TileDetailsDefaults.TitleRowTop, + end = TileDetailsDefaults.TitleRowEnd, + bottom = TileDetailsDefaults.TitleRowBottom + ), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { IconButton( onClick = { detailsViewModel.closeDetailedView() }, + colors = IconButtonDefaults.iconButtonColors( + contentColor = colors.onSurface + ), modifier = - Modifier.align(Alignment.CenterVertically) + Modifier + .align(Alignment.CenterVertically) .height(TileDetailsDefaults.IconHeight) + .width(TileDetailsDefaults.IconWidth) .padding(start = TileDetailsDefaults.IconPadding), ) { Icon( @@ -96,13 +113,19 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode text = title, modifier = Modifier.align(Alignment.CenterVertically), textAlign = TextAlign.Center, - style = MaterialTheme.typography.titleLarge, + style = MaterialTheme.typography.titleMedium, + color = colors.onSurface, ) IconButton( onClick = { tileDetailedViewModel.clickOnSettingsButton() }, + colors = IconButtonDefaults.iconButtonColors( + contentColor = colors.onSurface + ), modifier = - Modifier.align(Alignment.CenterVertically) + Modifier + .align(Alignment.CenterVertically) .height(TileDetailsDefaults.IconHeight) + .width(TileDetailsDefaults.IconWidth) .padding(end = TileDetailsDefaults.IconPadding), ) { Icon( @@ -116,7 +139,8 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode text = subTitle, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center, - style = MaterialTheme.typography.titleSmall, + style = MaterialTheme.typography.bodySmall, + color = colors.onSurfaceVariant, ) } MapTileDetailsContent(tileDetailedViewModel) @@ -135,6 +159,11 @@ private fun MapTileDetailsContent(tileDetailsViewModel: TileDetailsViewModel) { } private object TileDetailsDefaults { - val IconHeight = 48.dp + val IconHeight = 24.dp + val IconWidth = 24.dp val IconPadding = 4.dp + val TitleRowStart = 14.dp + val TitleRowTop = 22.dp + val TitleRowEnd = 20.dp + val TitleRowBottom = 8.dp } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt index 50012abc69d6..699778f3b6f9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt @@ -39,12 +39,14 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicText +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.key +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -102,6 +104,7 @@ fun LargeTileContent( sideDrawable: Drawable?, colors: TileColors, squishiness: () -> Float, + isVisible: () -> Boolean = { true }, accessibilityUiState: AccessibilityUiState? = null, iconShape: RoundedCornerShape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius), toggleClick: (() -> Unit)? = null, @@ -158,6 +161,7 @@ fun LargeTileContent( secondaryLabel = secondaryLabel, colors = colors, accessibilityUiState = accessibilityUiState, + isVisible = isVisible, ) if (sideDrawable != null) { @@ -170,12 +174,14 @@ fun LargeTileContent( } } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun LargeTileLabels( label: String, secondaryLabel: String?, colors: TileColors, modifier: Modifier = Modifier, + isVisible: () -> Boolean = { true }, accessibilityUiState: AccessibilityUiState? = null, ) { val animatedLabelColor by animateColorAsState(colors.label, label = "QSTileLabelColor") @@ -184,14 +190,16 @@ fun LargeTileLabels( Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) { TileLabel( text = label, - style = MaterialTheme.typography.labelLarge, + style = MaterialTheme.typography.titleSmallEmphasized, color = { animatedLabelColor }, + isVisible = isVisible, ) if (!TextUtils.isEmpty(secondaryLabel)) { TileLabel( secondaryLabel ?: "", color = { animatedSecondaryLabelColor }, - style = MaterialTheme.typography.bodyMedium, + style = MaterialTheme.typography.labelMedium, + isVisible = isVisible, modifier = Modifier.thenIf( accessibilityUiState?.stateDescription?.contains(secondaryLabel ?: "") == @@ -277,36 +285,50 @@ private fun TileLabel( color: ColorProducer, style: TextStyle, modifier: Modifier = Modifier, + isVisible: () -> Boolean = { true }, ) { + var textSize by remember { mutableIntStateOf(0) } + BasicText( text = text, color = color, style = style, maxLines = 1, + onTextLayout = { textSize = it.size.width }, modifier = modifier .fillMaxWidth() - .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } + .graphicsLayer { + if (textSize > size.width) { + compositingStrategy = CompositingStrategy.Offscreen + } + } .drawWithContent { drawContent() - // Draw a blur over the end of the text - val edgeWidthPx = TileLabelBlurWidth.toPx() - drawRect( - topLeft = Offset(size.width - edgeWidthPx, 0f), - size = Size(edgeWidthPx, size.height), - brush = - Brush.horizontalGradient( - colors = listOf(Color.Transparent, Color.Black), - startX = size.width, - endX = size.width - edgeWidthPx, - ), - blendMode = BlendMode.DstIn, - ) + if (textSize > size.width) { + // Draw a blur over the end of the text + val edgeWidthPx = TileLabelBlurWidth.toPx() + drawRect( + topLeft = Offset(size.width - edgeWidthPx, 0f), + size = Size(edgeWidthPx, size.height), + brush = + Brush.horizontalGradient( + colors = listOf(Color.Transparent, Color.Black), + startX = size.width, + endX = size.width - edgeWidthPx, + ), + blendMode = BlendMode.DstIn, + ) + } } - .basicMarquee( - iterations = TILE_MARQUEE_ITERATIONS, - initialDelayMillis = TILE_INITIAL_DELAY_MILLIS, - ), + .thenIf(isVisible()) { + // Only apply the marquee when the label is visible, which is needed for the + // always composed QS + Modifier.basicMarquee( + iterations = TILE_MARQUEE_ITERATIONS, + initialDelayMillis = TILE_INITIAL_DELAY_MILLIS, + ) + }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt index ccbd8fdbe00c..46f05d0ac895 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt @@ -65,6 +65,7 @@ import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Clear import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalContentColor @@ -109,11 +110,11 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.text.style.Hyphens import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastMap import com.android.compose.gesture.effect.rememberOffsetOverscrollEffectFactory import com.android.compose.modifiers.height @@ -165,7 +166,7 @@ import kotlinx.coroutines.launch object TileType -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) { val primaryContainerColor = MaterialTheme.colorScheme.primaryContainer @@ -177,7 +178,8 @@ private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) { ), title = { Text( - text = stringResource(id = R.string.qs_edit), + text = stringResource(id = R.string.qs_edit_tiles), + style = MaterialTheme.typography.titleLargeEmphasized, modifier = Modifier.padding(start = 24.dp), ) }, @@ -204,7 +206,10 @@ private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) { contentColor = MaterialTheme.colorScheme.onPrimary, ), ) { - Text(stringResource(id = com.android.internal.R.string.reset)) + Text( + text = stringResource(id = com.android.internal.R.string.reset), + style = MaterialTheme.typography.labelLarge, + ) } } }, @@ -212,6 +217,7 @@ private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) { ) } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun DefaultEditTileGrid( listState: EditTileListState, @@ -283,7 +289,9 @@ fun DefaultEditTileGrid( } } } else { - Text(text = stringResource(id = R.string.drag_to_rearrange_tiles)) + EditGridCenteredText( + text = stringResource(id = R.string.drag_to_rearrange_tiles) + ) } } } @@ -401,6 +409,11 @@ private fun EditGridHeader( } @Composable +private fun EditGridCenteredText(text: String, modifier: Modifier = Modifier) { + Text(text = text, style = MaterialTheme.typography.titleSmall, modifier = modifier) +} + +@Composable private fun RemoveTileTarget(onClick: () -> Unit) { Row( verticalAlignment = Alignment.CenterVertically, @@ -486,6 +499,7 @@ private fun CurrentTilesGrid( } } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable private fun AvailableTileGrid( tiles: List<AvailableTileGridCell>, @@ -524,7 +538,7 @@ private fun AvailableTileGrid( ) { Text( text = category.label.load() ?: "", - fontSize = 20.sp, + style = MaterialTheme.typography.titleMediumEmphasized, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.fillMaxWidth().padding(start = 8.dp, bottom = 16.dp), ) @@ -737,6 +751,7 @@ private fun TileGridCell( } } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable private fun AvailableTileGridCell( cell: AvailableTileGridCell, @@ -803,6 +818,7 @@ private fun AvailableTileGridCell( color = colors.label, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, + style = MaterialTheme.typography.labelMedium.copy(hyphens = Hyphens.Auto), modifier = Modifier.align(Alignment.TopCenter), ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt index 27e609232a4c..984343a45797 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt @@ -119,6 +119,7 @@ constructor( isLastInRow = isLastInColumn, ), detailsViewModel = detailsViewModel, + isVisible = listening, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt index 6bafd432669a..e24718285312 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt @@ -139,6 +139,7 @@ fun Tile( bounceableInfo: BounceableInfo, tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider, modifier: Modifier = Modifier, + isVisible: () -> Boolean = { true }, detailsViewModel: DetailsViewModel?, ) { trace(tile.traceName) { @@ -249,6 +250,7 @@ fun Tile( onLongClick = longClick, accessibilityUiState = uiState.accessibilityUiState, squishiness = squishiness, + isVisible = isVisible, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java index d2639654d206..5b9717535ab5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java @@ -78,6 +78,7 @@ import android.view.inputmethod.InputMethodManager; import androidx.annotation.NonNull; +import com.android.app.displaylib.PerDisplayRepository; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.AssistUtils; import com.android.internal.app.IVoiceInteractionSessionListener; @@ -90,7 +91,6 @@ import com.android.systemui.contextualeducation.GestureType; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.display.data.repository.DisplayRepository; -import com.android.systemui.display.data.repository.PerDisplayRepository; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardWmStateRefactor; @@ -126,8 +126,6 @@ import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInterface; -import dagger.Lazy; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -139,6 +137,8 @@ import java.util.function.Supplier; import javax.inject.Inject; import javax.inject.Provider; +import dagger.Lazy; + /** * Class to send information from SysUI to Launcher with a binder. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt index 74b3f302cdc8..b94e7b249233 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt @@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -36,8 +37,10 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.Dp import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.Expandable +import com.android.compose.modifiers.thenIf import com.android.systemui.animation.Expandable import com.android.systemui.common.ui.compose.Icon import com.android.systemui.common.ui.compose.load @@ -79,6 +82,17 @@ fun OngoingActivityChip( } is OngoingActivityChipModel.ClickBehavior.None -> null } + val isClickable = onClick != null + + val chipSidePadding = dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding) + val minWidth = + if (isClickable) { + dimensionResource(id = R.dimen.min_clickable_item_size) + } else if (model.icon != null) { + dimensionResource(id = R.dimen.ongoing_activity_chip_icon_size) + chipSidePadding + } else { + dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding + } Expandable( color = Color(model.colors.background(LocalContext.current).defaultColor), @@ -92,6 +106,15 @@ fun OngoingActivityChip( this.contentDescription = contentDescription } } + .thenIf(isClickable) { Modifier.widthIn(min = minWidth) } + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + layout(placeable.width, placeable.height) { + if (constraints.maxWidth >= minWidth.roundToPx()) { + placeable.place(0, 0) + } + } + } .graphicsLayer( alpha = if (model.transitionManager?.hideChipForTransition == true) { @@ -103,9 +126,12 @@ fun OngoingActivityChip( borderStroke = borderStroke, onClick = onClick, useModifierBasedImplementation = StatusBarChipsReturnAnimations.isEnabled, + // Some chips like the 3-2-1 countdown chip should be very small, smaller than a + // reasonable minimum size. + defaultMinSize = false, transitionControllerFactory = model.transitionManager?.controllerFactory, ) { - ChipBody(model, iconViewStore, isClickable = onClick != null) + ChipBody(model, iconViewStore, isClickable = isClickable, minWidth = minWidth) } } @@ -114,36 +140,22 @@ private fun ChipBody( model: OngoingActivityChipModel.Active, iconViewStore: NotificationIconContainerViewBinder.IconViewStore?, isClickable: Boolean, + minWidth: Dp, modifier: Modifier = Modifier, ) { val hasEmbeddedIcon = model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView || model.icon is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon - val chipSidePadding = dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding) - val minWidth = - if (isClickable) { - dimensionResource(id = R.dimen.min_clickable_item_size) - } else if (model.icon != null) { - dimensionResource(id = R.dimen.ongoing_activity_chip_icon_size) + chipSidePadding - } else { - dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding - } - Row( horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, modifier = modifier .fillMaxHeight() - .layout { measurable, constraints -> - val placeable = measurable.measure(constraints) - layout(placeable.width, placeable.height) { - if (constraints.maxWidth >= minWidth.roundToPx()) { - placeable.place(0, 0) - } - } - } + // Set the minWidth here as well as on the Expandable so that the content within + // this row is still centered correctly horizontally + .thenIf(isClickable) { Modifier.widthIn(min = minWidth) } .padding( horizontal = if (hasEmbeddedIcon) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt index 3168a22c56ad..cb1002a83179 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt @@ -17,14 +17,14 @@ package com.android.systemui.statusbar.data.repository import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR +import com.android.app.displaylib.PerDisplayInstanceProvider +import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl +import com.android.app.displaylib.PerDisplayRepository +import com.android.app.displaylib.SingleInstanceRepositoryImpl import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.common.ui.ConfigurationStateImpl import com.android.systemui.dagger.SysUISingleton import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository -import com.android.systemui.display.data.repository.PerDisplayInstanceProvider -import com.android.systemui.display.data.repository.PerDisplayInstanceRepositoryImpl -import com.android.systemui.display.data.repository.PerDisplayRepository -import com.android.systemui.display.data.repository.SingleInstanceRepositoryImpl import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import dagger.Lazy import dagger.Module @@ -39,7 +39,6 @@ constructor( private val statusBarConfigurationControllerStore: StatusBarConfigurationControllerStore, private val factory: ConfigurationStateImpl.Factory, ) : PerDisplayInstanceProvider<ConfigurationState> { - override fun createInstance(displayId: Int): ConfigurationState? { val displayWindowProperties = displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) ?: return null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java index fcdcc3f698de..fed9417edd88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -23,6 +23,7 @@ import android.view.View; import com.android.systemui.DejankUtils; import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; @@ -44,13 +45,20 @@ public final class NotificationClicker implements View.OnClickListener { private final Optional<Bubbles> mBubblesOptional; private final NotificationActivityStarter mNotificationActivityStarter; - private ExpandableNotificationRow.OnDragSuccessListener mOnDragSuccessListener = - new ExpandableNotificationRow.OnDragSuccessListener() { - @Override - public void onDragSuccess(NotificationEntry entry) { - mNotificationActivityStarter.onDragSuccess(entry); - } - }; + private ExpandableNotificationRow.OnDragSuccessListener mOnDragSuccessListener + = new ExpandableNotificationRow.OnDragSuccessListener() { + @Override + public void onDragSuccess(NotificationEntry entry) { + NotificationBundleUi.assertInLegacyMode(); + mNotificationActivityStarter.onDragSuccess(entry); + } + + @Override + public void onDragSuccess(EntryAdapter entryAdapter) { + NotificationBundleUi.isUnexpectedlyInLegacyMode(); + entryAdapter.onDragSuccess(); + } + }; private NotificationClicker( NotificationClickerLogger logger, @@ -73,7 +81,6 @@ public final class NotificationClicker implements View.OnClickListener { mPowerInteractor.wakeUpIfDozing("NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE); final ExpandableNotificationRow row = (ExpandableNotificationRow) v; - final NotificationEntry entry = row.getEntry(); mLogger.logOnClick(row.getLoggingKey()); // Check if the notification is displaying the menu, if so slide notification back @@ -101,16 +108,16 @@ public final class NotificationClicker implements View.OnClickListener { DejankUtils.postAfterTraversal(() -> row.setJustClicked(false)); if (NotificationBundleUi.isEnabled()) { - if (!row.getEntryAdapter().isBubbleCapable() && mBubblesOptional.isPresent()) { + if (!row.getEntryAdapter().isBubble() && mBubblesOptional.isPresent()) { mBubblesOptional.get().collapseStack(); } + row.getEntryAdapter().onEntryClicked(row); } else { if (!row.getEntryLegacy().isBubble() && mBubblesOptional.isPresent()) { mBubblesOptional.get().collapseStack(); } + mNotificationActivityStarter.onNotificationClicked(row.getEntryLegacy(), row); } - - mNotificationActivityStarter.onNotificationClicked(entry, row); } private boolean isMenuVisible(ExpandableNotificationRow row) { @@ -121,9 +128,12 @@ public final class NotificationClicker implements View.OnClickListener { * Attaches the click listener to the row if appropriate. */ public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { + boolean isBubble = NotificationBundleUi.isEnabled() + ? row.getEntryAdapter().isBubble() + : row.getEntryLegacy().isBubble(); Notification notification = sbn.getNotification(); if (notification.contentIntent != null || notification.fullScreenIntent != null - || row.getEntry().isBubble()) { + || isBubble) { if (NotificationBundleUi.isEnabled()) { row.setBubbleClickListener( v -> row.getEntryAdapter().onNotificationBubbleIconClicked()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt index 1fc8efdcf24a..be17ae56c315 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt @@ -22,6 +22,7 @@ import android.os.Build import android.service.notification.StatusBarNotification import android.util.Log import com.android.systemui.statusbar.notification.icon.IconPack +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import kotlinx.coroutines.flow.StateFlow @@ -97,7 +98,7 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter { return false } - override fun isBubbleCapable(): Boolean { + override fun isBubble(): Boolean { return false } @@ -113,6 +114,10 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter { return false } + override fun getPeopleNotificationType(): Int { + return TYPE_NON_PERSON + } + override fun isPromotedOngoing(): Boolean { return false } @@ -121,6 +126,11 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter { return false } + override fun onDragSuccess() { + // do nothing. these should not be draggable + Log.wtf(TAG, "onDragSuccess() called") + } + override fun onNotificationBubbleIconClicked() { // do nothing. these cannot be a bubble Log.wtf(TAG, "onNotificationBubbleIconClicked() called") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java index 47ffe1030eff..3757ebfb9986 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java @@ -23,6 +23,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.statusbar.notification.icon.IconPack; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import kotlinx.coroutines.flow.StateFlow; @@ -124,7 +125,7 @@ public interface EntryAdapter { boolean canDragAndDrop(); - boolean isBubbleCapable(); + boolean isBubble(); @Nullable String getStyle(); @@ -132,6 +133,8 @@ public interface EntryAdapter { boolean isAmbient(); + @PeopleNotificationIdentifier.Companion.PeopleNotificationType int getPeopleNotificationType(); + /** * Returns whether this row represents promoted ongoing notification. */ @@ -141,6 +144,8 @@ public interface EntryAdapter { return false; } + void onDragSuccess(); + /** * Process a click on a notification bubble icon */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt index f78a5ddd3982..a23c5a3ea9f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt @@ -121,7 +121,7 @@ class NotificationEntryAdapter( return false } - override fun isBubbleCapable(): Boolean { + override fun isBubble(): Boolean { return entry.isBubble } @@ -137,6 +137,10 @@ class NotificationEntryAdapter( return entry.ranking.isAmbient } + override fun getPeopleNotificationType(): Int { + return peopleNotificationIdentifier.getPeopleNotificationType(entry) + } + override fun isPromotedOngoing(): Boolean { return entry.isPromotedOngoing } @@ -145,6 +149,10 @@ class NotificationEntryAdapter( return entry.sbn.notification.fullScreenIntent != null } + override fun onDragSuccess() { + notificationActivityStarter.onDragSuccess(entry) + } + override fun onNotificationBubbleIconClicked() { notificationActivityStarter.onNotificationBubbleIconClicked(entry) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt index 147a5afea306..619d48f8ba81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt @@ -18,9 +18,9 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder import android.view.Display import androidx.lifecycle.lifecycleScope +import com.android.app.displaylib.PerDisplayRepository import com.android.app.tracing.traceSection import com.android.systemui.common.ui.ConfigurationState -import com.android.systemui.display.data.repository.PerDisplayRepository import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt index d35c3b617246..7e19ff115a92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt @@ -21,6 +21,7 @@ import android.app.Notification.BigPictureStyle import android.app.Notification.BigTextStyle import android.app.Notification.CallStyle import android.app.Notification.EXTRA_BIG_TEXT +import android.app.Notification.EXTRA_CALL_PERSON import android.app.Notification.EXTRA_CHRONOMETER_COUNT_DOWN import android.app.Notification.EXTRA_PROGRESS import android.app.Notification.EXTRA_PROGRESS_INDETERMINATE @@ -33,6 +34,7 @@ import android.app.Notification.EXTRA_VERIFICATION_ICON import android.app.Notification.EXTRA_VERIFICATION_TEXT import android.app.Notification.InboxStyle import android.app.Notification.ProgressStyle +import android.app.Person import android.content.Context import android.graphics.drawable.Icon import com.android.systemui.Flags @@ -108,12 +110,12 @@ constructor( contentBuilder.shortCriticalText = notification.shortCriticalText() contentBuilder.lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs contentBuilder.profileBadgeResId = null // TODO - contentBuilder.title = notification.resolveTitle(recoveredBuilder.style) - contentBuilder.text = notification.resolveText(recoveredBuilder.style) + contentBuilder.title = notification.title(recoveredBuilder.style) + contentBuilder.text = notification.text(recoveredBuilder.style) contentBuilder.skeletonLargeIcon = notification.skeletonLargeIcon(imageModelProvider) contentBuilder.oldProgress = notification.oldProgress() - val colorsFromNotif = recoveredBuilder.getColors(/* header= */ false) + val colorsFromNotif = recoveredBuilder.getColors(/* isHeader= */ false) contentBuilder.colors = PromotedNotificationContentModel.Colors( backgroundColor = colorsFromNotif.backgroundColor, @@ -132,20 +134,16 @@ constructor( private fun Notification.bigTitle(): CharSequence? = extras?.getCharSequence(EXTRA_TITLE_BIG) - private fun Notification.Style.bigTitleOverridesTitle(): Boolean { - return when (this) { + private fun Notification.callPerson(): Person? = + extras?.getParcelable(EXTRA_CALL_PERSON, Person::class.java) + + private fun Notification.title(style: Notification.Style?): CharSequence? { + return when (style) { is BigTextStyle, is BigPictureStyle, - is InboxStyle -> true - else -> false - } - } - - private fun Notification.resolveTitle(style: Notification.Style?): CharSequence? { - return if (style?.bigTitleOverridesTitle() == true) { - bigTitle() - } else { - null + is InboxStyle -> bigTitle() + is CallStyle -> callPerson()?.name + else -> null } ?: title() } @@ -153,13 +151,10 @@ constructor( private fun Notification.bigText(): CharSequence? = extras?.getCharSequence(EXTRA_BIG_TEXT) - private fun Notification.Style.bigTextOverridesText(): Boolean = this is BigTextStyle - - private fun Notification.resolveText(style: Notification.Style?): CharSequence? { - return if (style?.bigTextOverridesText() == true) { - bigText() - } else { - null + private fun Notification.text(style: Notification.Style?): CharSequence? { + return when (style) { + is BigTextStyle -> bigText() + else -> null } ?: text() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt index 468934791525..5f9678a06b59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt @@ -63,7 +63,7 @@ constructor(@PromotedNotificationLog private val buffer: LogBuffer) { INFO, { str1 = entry.logKey - str2 = content.toString() + str2 = content.toRedactedString() }, { "extraction succeeded: $str2 for $str1" }, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt index 0c2859fa1766..57b07204fc6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt @@ -25,6 +25,8 @@ import androidx.annotation.ColorInt import com.android.internal.widget.NotificationProgressModel import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi +import com.android.systemui.statusbar.notification.row.ImageResult +import com.android.systemui.statusbar.notification.row.LazyImage import com.android.systemui.statusbar.notification.row.shared.ImageModel /** @@ -152,6 +154,54 @@ data class PromotedNotificationContentModel( Ineligible, } + fun toRedactedString(): String { + return ("PromotedNotificationContentModel(" + + "identity=$identity, " + + "wasPromotedAutomatically=$wasPromotedAutomatically, " + + "smallIcon=${smallIcon?.toRedactedString()}, " + + "appName=$appName, " + + "subText=${subText?.toRedactedString()}, " + + "shortCriticalText=$shortCriticalText, " + + "time=$time, " + + "lastAudiblyAlertedMs=$lastAudiblyAlertedMs, " + + "profileBadgeResId=$profileBadgeResId, " + + "title=${title?.toRedactedString()}, " + + "text=${text?.toRedactedString()}, " + + "skeletonLargeIcon=${skeletonLargeIcon?.toRedactedString()}, " + + "oldProgress=$oldProgress, " + + "colors=$colors, " + + "style=$style, " + + "personIcon=${personIcon?.toRedactedString()}, " + + "personName=${personName?.toRedactedString()}, " + + "verificationIcon=$verificationIcon, " + + "verificationText=$verificationText, " + + "newProgress=$newProgress)") + } + + private fun CharSequence.toRedactedString(): String = "[$length]" + + private fun ImageModel.toRedactedString(): String { + return when (this) { + is LazyImage -> this.toRedactedString() + else -> this.toString() + } + } + + private fun LazyImage.toRedactedString(): String { + return ("LazyImage(" + + "icon=[${icon.javaClass.simpleName}], " + + "sizeClass=$sizeClass, " + + "transform=$transform, " + + "result=${result?.toRedactedString()})") + } + + private fun ImageResult.toRedactedString(): String { + return when (this) { + is ImageResult.Empty -> this.toString() + is ImageResult.Image -> "Image(drawable=[${drawable.javaClass.simpleName}])" + } + } + companion object { @JvmStatic fun featureFlagEnabled(): Boolean = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 1a76d5d9d9e4..2a3b266c8d10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -22,9 +22,9 @@ import static android.service.notification.NotificationListenerService.REASON_CA import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_EXPANDED; import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; +import static com.android.systemui.Flags.notificationRowAccessibilityExpanded; import static com.android.systemui.Flags.notificationRowTransparency; import static com.android.systemui.Flags.notificationsPinnedHunInShade; -import static com.android.systemui.Flags.notificationRowAccessibilityExpanded; import static com.android.systemui.flags.Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE; import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED; @@ -671,8 +671,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } private boolean isConversation() { - return mPeopleNotificationIdentifier.getPeopleNotificationType(getEntry()) - != PeopleNotificationIdentifier.TYPE_NON_PERSON; + if (NotificationBundleUi.isEnabled()) { + return getEntryAdapter().getPeopleNotificationType() + != PeopleNotificationIdentifier.TYPE_NON_PERSON; + } else { + return mPeopleNotificationIdentifier.getPeopleNotificationType(getEntryLegacy()) + != PeopleNotificationIdentifier.TYPE_NON_PERSON; + } } public void onNotificationUpdated() { @@ -2515,7 +2520,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ public void dragAndDropSuccess() { if (mOnDragSuccessListener != null) { - mOnDragSuccessListener.onDragSuccess(getEntry()); + if (NotificationBundleUi.isEnabled()) { + mOnDragSuccessListener.onDragSuccess(getEntryAdapter()); + } else { + mOnDragSuccessListener.onDragSuccess(getEntryLegacy()); + } } } @@ -4002,7 +4011,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } else if (isChildInGroup()) { final int childColor = getShowingLayout().getBackgroundColorForExpansionState(); - if (Flags.notificationRowTransparency() && childColor == Color.TRANSPARENT) { + if ((Flags.notificationRowTransparency() || notificationsRedesignTemplates()) + && childColor == Color.TRANSPARENT) { // If child is not customizing its background color, switch from the parent to // the child background when the expansion finishes. mShowNoBackground = !mNotificationParent.mShowNoBackground; @@ -4416,6 +4426,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * @param entry NotificationEntry that succeed to drop on proper target window. */ void onDragSuccess(NotificationEntry entry); + + /** + * @param entryAdapter The EntryAdapter that successfully dropped on the proper + * target window + */ + void onDragSuccess(EntryAdapter entryAdapter); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt index fe3a856e711e..a9ca6359b570 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt @@ -62,6 +62,7 @@ class BaseBackgroundDrawable( private val buttonShape = Path() // Color and style + private val outlineStaticColor = context.getColor(R.color.magic_action_button_stroke_color) private val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { val bgColor = context.getColor( @@ -70,15 +71,17 @@ class BaseBackgroundDrawable( color = bgColor style = Paint.Style.FILL } - private val outlinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - val outlineColor = - context.getColor( - com.android.internal.R.color.materialColorOutlineVariant - ) - color = outlineColor + private val outlineGradientPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = outlineStaticColor + style = Paint.Style.STROKE + strokeWidth = outlineStrokeWidth + } + private val outlineSolidPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = outlineStaticColor style = Paint.Style.STROKE strokeWidth = outlineStrokeWidth } + private val outlineStartColor = context.getColor( com.android.internal.R.color.materialColorTertiaryContainer @@ -91,21 +94,35 @@ class BaseBackgroundDrawable( context.getColor( com.android.internal.R.color.materialColorPrimary ) + // Animation private var gradientAnimator: ValueAnimator private var rotationAngle = 20f // Start rotation at 20 degrees + private var fadeAnimator: ValueAnimator? = null + private var gradientAlpha = 255 // Fading out gradient + private var solidAlpha = 0 // Fading in solid color init { gradientAnimator = ValueAnimator.ofFloat(0f, 1f).apply { - duration = 5000 // 5 seconds + duration = 1500 interpolator = Interpolators.LINEAR - repeatCount = 1 + repeatCount = 0 addUpdateListener { animator -> val animatedValue = animator.animatedValue as Float rotationAngle = 20f + animatedValue * 360f // Rotate in a spiral invalidateSelf() } - // TODO: Reset the outline color when animation ends. + start() + } + fadeAnimator = ValueAnimator.ofFloat(0f, 1f).apply { + duration = 500 + startDelay = 1000 + addUpdateListener { animator -> + val progress = animator.animatedValue as Float + gradientAlpha = ((1 - progress) * 255).toInt() // Fade out gradient + solidAlpha = (progress * 255).toInt() // Fade in color + invalidateSelf() + } start() } } @@ -120,14 +137,9 @@ class BaseBackgroundDrawable( // Draw background canvas.clipPath(buttonShape) canvas.drawPath(buttonShape, bgPaint) - // Apply gradient to outline - canvas.drawPath(buttonShape, outlinePaint) - updateGradient(boundsF) - canvas.restore() - } - private fun updateGradient(boundsF: RectF) { - val gradient = LinearGradient( + // Set up outline gradient + val gradientShader = LinearGradient( boundsF.left, boundsF.top, boundsF.right, boundsF.bottom, intArrayOf(outlineStartColor, outlineMiddleColor, outlineEndColor), @@ -137,9 +149,17 @@ class BaseBackgroundDrawable( // Create a rotation matrix for the spiral effect val matrix = Matrix() matrix.setRotate(rotationAngle, boundsF.centerX(), boundsF.centerY()) - gradient.setLocalMatrix(matrix) + gradientShader.setLocalMatrix(matrix) + + // Apply gradient to outline + outlineGradientPaint.shader = gradientShader + outlineGradientPaint.alpha = gradientAlpha + canvas.drawPath(buttonShape, outlineGradientPaint) + // Apply solid color to outline + outlineSolidPaint.alpha = solidAlpha + canvas.drawPath(buttonShape, outlineSolidPaint) - outlinePaint.shader = gradient + canvas.restore() } override fun onBoundsChange(bounds: Rect) { @@ -149,13 +169,15 @@ class BaseBackgroundDrawable( override fun setAlpha(alpha: Int) { bgPaint.alpha = alpha - outlinePaint.alpha = alpha + outlineGradientPaint.alpha = alpha + outlineSolidPaint.alpha = alpha invalidateSelf() } override fun setColorFilter(colorFilter: ColorFilter?) { bgPaint.colorFilter = colorFilter - outlinePaint.colorFilter = colorFilter + outlineGradientPaint.colorFilter = colorFilter + outlineSolidPaint.colorFilter = colorFilter invalidateSelf() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 26d318bea5cc..488aa44ddd3b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1604,12 +1604,15 @@ public class NotificationContentView extends FrameLayout implements Notification } if (shouldShowBubbleButton(entry)) { + boolean isBubble = NotificationBundleUi.isEnabled() + ? mContainingNotification.getEntryAdapter().isBubble() + : entry.isBubble(); // explicitly resolve drawable resource using SystemUI's theme - Drawable d = mContext.getDrawable(entry.isBubble() + Drawable d = mContext.getDrawable(isBubble ? com.android.wm.shell.R.drawable.bubble_ic_stop_bubble : com.android.wm.shell.R.drawable.bubble_ic_create_bubble); - String contentDescription = mContext.getResources().getString(entry.isBubble() + String contentDescription = mContext.getResources().getString(isBubble ? R.string.notification_conversation_unbubble : R.string.notification_conversation_bubble); @@ -1652,12 +1655,18 @@ public class NotificationContentView extends FrameLayout implements Notification @VisibleForTesting boolean shouldShowBubbleButton(NotificationEntry entry) { - boolean isPersonWithShortcut = - mPeopleIdentifier.getPeopleNotificationType(entry) - >= PeopleNotificationIdentifier.TYPE_FULL_PERSON; + int peopleType = NotificationBundleUi.isEnabled() + ? mContainingNotification.getEntryAdapter().getPeopleNotificationType() + : mPeopleIdentifier.getPeopleNotificationType(entry); + Notification.BubbleMetadata bubbleMetadata = NotificationBundleUi.isEnabled() + ? mContainingNotification.getEntryAdapter().getSbn().getNotification() + .getBubbleMetadata() + : entry.getBubbleMetadata(); + boolean isPersonWithShortcut = peopleType + >= PeopleNotificationIdentifier.TYPE_FULL_PERSON; return mBubblesEnabledForUser && isPersonWithShortcut - && entry.getBubbleMetadata() != null; + && bubbleMetadata != null; } private void applySnoozeAction(View layout) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index e89a76fd5a69..977936fa34fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -49,6 +49,7 @@ import com.android.systemui.statusbar.AlphaOptimizedImageView; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import java.util.ArrayList; @@ -261,15 +262,19 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl mSnoozeItem = createSnoozeItem(mContext); } mFeedbackItem = createFeedbackItem(mContext); - NotificationEntry entry = mParent.getEntry(); - int personNotifType = mPeopleNotificationIdentifier.getPeopleNotificationType(entry); + int personNotifType = NotificationBundleUi.isEnabled() + ? mParent.getEntryAdapter().getPeopleNotificationType() + : mPeopleNotificationIdentifier.getPeopleNotificationType(mParent.getEntryLegacy()); + StatusBarNotification sbn = NotificationBundleUi.isEnabled() + ? mParent.getEntryAdapter().getSbn() + : mParent.getEntryLegacy().getSbn(); if (personNotifType == PeopleNotificationIdentifier.TYPE_PERSON) { mInfoItem = createPartialConversationItem(mContext); } else if (personNotifType >= PeopleNotificationIdentifier.TYPE_FULL_PERSON) { mInfoItem = createConversationItem(mContext); } else if (android.app.Flags.uiRichOngoing() && Flags.permissionHelperUiRichOngoing() - && entry.getSbn().getNotification().isPromotedOngoing()) { + && sbn.getNotification().isPromotedOngoing()) { mInfoItem = createPromotedItem(mContext); } else { mInfoItem = createInfoItem(mContext); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt index 08c1d71b86c9..03990bf3af84 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt @@ -87,9 +87,15 @@ constructor(private val userManager: UserManager, dumpManager: DumpManager) : // It's not a system app at all. return false } else { - // If there's no launch intent, it's probably a headless app. - val pm = context.packageManager - return (pm.getLaunchIntentForPackage(info.packageName) == null) + // If there's no launch intent, it's probably a headless app. Check for both + // direct-aware and -unaware intents; otherwise this will almost certainly fail + // for notifications posted before unlocking. + val packageLaunchIntent = + context.packageManager.getLaunchIntentForPackage( + info.packageName, + /* includeDirectBootUnaware= */ true, + ) + return packageLaunchIntent == null } } else { // If for some reason we don't have the app info, we don't know; best assume it's diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index a4ee4ad6f6ec..9d9f01b571a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -31,11 +31,11 @@ import static androidx.lifecycle.Lifecycle.State.RESUMED; import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import static com.android.systemui.Flags.keyboardShortcutHelperRewrite; -import static com.android.systemui.Flags.lightRevealMigration; import static com.android.systemui.Flags.relockWithPowerButtonImmediately; import static com.android.systemui.Flags.statusBarSignalPolicyRefactor; import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL; import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT; +import static com.android.systemui.shared.Flags.ambientAod; import static com.android.systemui.statusbar.StatusBarState.SHADE; import android.annotation.Nullable; @@ -980,7 +980,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void onKeyguardGoingAwayChanged() { - if (lightRevealMigration()) { + if (ambientAod()) { // This code path is not used if the KeyguardTransitionRepository is managing // the lightreveal scrim. return; @@ -2446,7 +2446,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return; } - if (lightRevealMigration()) { + if (ambientAod()) { return; } @@ -3103,7 +3103,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void onDozeAmountChanged(float linear, float eased) { - if (!lightRevealMigration() + if (!ambientAod() && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { // If wakeAndUnlocking, this is handled in AuthRippleInteractor if (!mBiometricUnlockController.isWakeAndUnlock()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 36193bd87ce2..3c144625b685 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -49,6 +49,7 @@ import com.android.keyguard.CarrierTextController; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.logging.KeyguardLogger; +import com.android.systemui.Flags; import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor; import com.android.systemui.dagger.qualifiers.Background; @@ -475,12 +476,14 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat UserHandle.USER_ALL); updateUserSwitcher(); onThemeChanged(); - collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer, - mCoroutineDispatcher); - collectFlow(mView, mLockscreenToHubTransitionViewModel.getStatusBarAlpha(), - mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher); - collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(), - mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher); + if (!Flags.glanceableHubV2()) { + collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer, + mCoroutineDispatcher); + collectFlow(mView, mLockscreenToHubTransitionViewModel.getStatusBarAlpha(), + mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher); + collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(), + mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher); + } if (NewStatusBarIcons.isEnabled()) { ComposeView batteryComposeView = new ComposeView(mContext); UnifiedBatteryViewBinder.bind( @@ -645,7 +648,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat && !mDozing && !hideForBypass && !mDisableStateTracker.isDisabled() - && (!mCommunalShowing || mExplicitAlpha != -1) + && (Flags.glanceableHubV2() || (!mCommunalShowing || mExplicitAlpha != -1)) ? View.VISIBLE : View.INVISIBLE; updateViewState(newAlpha, newVisibility); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index 0d43789e95a8..8890db3cd943 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -18,7 +18,6 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD import com.android.systemui.DejankUtils -import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.KeyguardViewMediator @@ -26,6 +25,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor +import com.android.systemui.shared.Flags.ambientAod import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.NotificationShadeWindowController @@ -100,7 +100,7 @@ constructor( duration = LIGHT_REVEAL_ANIMATION_DURATION interpolator = Interpolators.LINEAR addUpdateListener { - if (lightRevealMigration()) return@addUpdateListener + if (ambientAod()) return@addUpdateListener if (lightRevealScrim.revealEffect !is CircleReveal) { lightRevealScrim.revealAmount = it.animatedValue as Float } @@ -116,7 +116,7 @@ constructor( addListener( object : AnimatorListenerAdapter() { override fun onAnimationCancel(animation: Animator) { - if (lightRevealMigration()) return + if (ambientAod()) return if (lightRevealScrim.revealEffect !is CircleReveal) { lightRevealScrim.revealAmount = 1f } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt index 1a8ca9577bd7..f4afc248d11a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt @@ -22,6 +22,7 @@ import com.android.systemui.KairosBuilder import com.android.systemui.kairos.BuildSpec import com.android.systemui.kairos.ExperimentalKairosApi import com.android.systemui.kairos.State +import com.android.systemui.kairos.combine import com.android.systemui.kairos.flatMap import com.android.systemui.kairosBuilder import com.android.systemui.log.table.TableLogBuffer @@ -55,9 +56,15 @@ constructor( @Assisted private val isCarrierMerged: State<Boolean>, ) : MobileConnectionRepositoryKairos, KairosBuilder by kairosBuilder() { + private var dumpCache: DumpCache? = null + init { onActivated { logDiffsForTable(isCarrierMerged, tableLogBuffer, columnName = "isCarrierMerged") + combine(isCarrierMerged, activeRepo) { isCarrierMerged, activeRepo -> + DumpCache(isCarrierMerged, activeRepo) + } + .observe { dumpCache = it } } } @@ -198,13 +205,6 @@ constructor( override val isInEcmMode: State<Boolean> = activeRepo.flatMap { it.isInEcmMode } - private var dumpCache: DumpCache? = null - - private data class DumpCache( - val isCarrierMerged: Boolean, - val activeRepo: MobileConnectionRepositoryKairos, - ) - fun dump(pw: PrintWriter) { val cache = dumpCache ?: return val ipw = IndentingPrintWriter(pw, " ") @@ -227,6 +227,11 @@ constructor( ipw.decreaseIndent() } + private data class DumpCache( + val isCarrierMerged: Boolean, + val activeRepo: MobileConnectionRepositoryKairos, + ) + @AssistedFactory interface Factory { fun create( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt index e46815954e64..e6c29214e3d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt @@ -131,6 +131,8 @@ constructor( private val mobileRepoFactory: Lazy<ConnectionRepoFactory>, ) : MobileConnectionsRepositoryKairos, Dumpable, KairosBuilder by kairosBuilder() { + private var dumpCache: DumpCache? = null + init { dumpManager.registerNormalDumpable("MobileConnectionsRepositoryKairos", this) } @@ -253,6 +255,7 @@ constructor( .asIncremental() .mapValues { (subId, sub) -> mobileRepoFactory.get().create(subId) } .applyLatestSpecForKey() + .apply { observe { dumpCache = DumpCache(it) } } } private val telephonyManagerState: State<Pair<Int?, Set<Int>>> = buildState { @@ -479,10 +482,6 @@ constructor( profileClass = profileClass, ) - private var dumpCache: DumpCache? = null - - private data class DumpCache(val repos: Map<Int, FullMobileConnectionRepositoryKairos>) - override fun dump(pw: PrintWriter, args: Array<String>) { val cache = dumpCache ?: return val ipw = IndentingPrintWriter(pw, " ") @@ -494,10 +493,16 @@ constructor( ipw.println("Connections (${cache.repos.size} total):") ipw.increaseIndent() - cache.repos.values.forEach { it.dump(ipw) } + cache.repos.values.forEach { + if (it is FullMobileConnectionRepositoryKairos) { + it.dump(ipw) + } + } ipw.decreaseIndent() } + private data class DumpCache(val repos: Map<Int, MobileConnectionRepositoryKairos>) + fun interface ConnectionRepoFactory { fun create(subId: Int): BuildSpec<MobileConnectionRepositoryKairos> } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index 0eabb4ecee84..af4e61afaaaa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -61,31 +61,31 @@ interface MobileIconInteractor { * consider this connection to be serving data, and thus want to show a network type icon, when * data is connected. Other data connection states would typically cause us not to show the icon */ - val isDataConnected: StateFlow<Boolean> + val isDataConnected: Flow<Boolean> /** True if we consider this connection to be in service, i.e. can make calls */ - val isInService: StateFlow<Boolean> + val isInService: Flow<Boolean> /** True if this connection is emergency only */ - val isEmergencyOnly: StateFlow<Boolean> + val isEmergencyOnly: Flow<Boolean> /** Observable for the data enabled state of this connection */ - val isDataEnabled: StateFlow<Boolean> + val isDataEnabled: Flow<Boolean> /** True if the RAT icon should always be displayed and false otherwise. */ - val alwaysShowDataRatIcon: StateFlow<Boolean> + val alwaysShowDataRatIcon: Flow<Boolean> /** Canonical representation of the current mobile signal strength as a triangle. */ - val signalLevelIcon: StateFlow<SignalIconModel> + val signalLevelIcon: Flow<SignalIconModel> /** Observable for RAT type (network type) indicator */ - val networkTypeIconGroup: StateFlow<NetworkTypeIconModel> + val networkTypeIconGroup: Flow<NetworkTypeIconModel> /** Whether or not to show the slice attribution */ - val showSliceAttribution: StateFlow<Boolean> + val showSliceAttribution: Flow<Boolean> /** True if this connection is satellite-based */ - val isNonTerrestrial: StateFlow<Boolean> + val isNonTerrestrial: Flow<Boolean> /** * Provider name for this network connection. The name can be one of 3 values: @@ -95,7 +95,7 @@ interface MobileIconInteractor { * override in [connectionInfo.operatorAlphaShort], a value that is derived from * [ServiceState] */ - val networkName: StateFlow<NetworkNameModel> + val networkName: Flow<NetworkNameModel> /** * Provider name for this network connection. The name can be one of 3 values: @@ -108,26 +108,26 @@ interface MobileIconInteractor { * TODO(b/296600321): De-duplicate this field with [networkName] after determining the data * provided is identical */ - val carrierName: StateFlow<String> + val carrierName: Flow<String> /** True if there is only one active subscription. */ - val isSingleCarrier: StateFlow<Boolean> + val isSingleCarrier: Flow<Boolean> /** * True if this connection is considered roaming. The roaming bit can come from [ServiceState], * or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a * connection to be roaming while carrier network change is active */ - val isRoaming: StateFlow<Boolean> + val isRoaming: Flow<Boolean> /** See [MobileIconsInteractor.isForceHidden]. */ val isForceHidden: Flow<Boolean> /** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */ - val isAllowedDuringAirplaneMode: StateFlow<Boolean> + val isAllowedDuringAirplaneMode: Flow<Boolean> /** True when in carrier network change mode */ - val carrierNetworkChangeActive: StateFlow<Boolean> + val carrierNetworkChangeActive: Flow<Boolean> } /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt index 87877b3e9f43..6b9c5374ef2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt @@ -32,11 +32,20 @@ import com.android.systemui.kairos.map import com.android.systemui.kairos.mapValues import com.android.systemui.kairos.toColdConflatedFlow import com.android.systemui.kairosBuilder +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableLogBufferFactory +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairos +import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE +import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName +import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository +import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import dagger.Provides import dagger.multibindings.ElementsIntoSet import javax.inject.Inject @@ -45,6 +54,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @ExperimentalKairosApi @@ -60,6 +71,7 @@ constructor( context: Context, mobileMappingsProxy: MobileMappingsProxy, private val userSetupRepo: UserSetupRepository, + private val logFactory: TableLogBufferFactory, ) : MobileIconsInteractor, KairosBuilder by kairosBuilder() { private val interactorsBySubIdK = buildIncremental { @@ -158,7 +170,37 @@ constructor( get() = repo.isDeviceEmergencyCallCapable override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor = - interactorsBySubId.value[subId] ?: error("Unknown subscription id: $subId") + object : MobileIconInteractor { + override val tableLogBuffer: TableLogBuffer = + logFactory.getOrCreate(tableBufferLogName(subId), MOBILE_CONNECTION_BUFFER_SIZE) + override val activity: Flow<DataActivityModel> = latest { activity } + override val mobileIsDefault: Flow<Boolean> = latest { mobileIsDefault } + override val isDataConnected: Flow<Boolean> = latest { isDataConnected } + override val isInService: Flow<Boolean> = latest { isInService } + override val isEmergencyOnly: Flow<Boolean> = latest { isEmergencyOnly } + override val isDataEnabled: Flow<Boolean> = latest { isDataEnabled } + override val alwaysShowDataRatIcon: Flow<Boolean> = latest { alwaysShowDataRatIcon } + override val signalLevelIcon: Flow<SignalIconModel> = latest { signalLevelIcon } + override val networkTypeIconGroup: Flow<NetworkTypeIconModel> = latest { + networkTypeIconGroup + } + override val showSliceAttribution: Flow<Boolean> = latest { showSliceAttribution } + override val isNonTerrestrial: Flow<Boolean> = latest { isNonTerrestrial } + override val networkName: Flow<NetworkNameModel> = latest { networkName } + override val carrierName: Flow<String> = latest { carrierName } + override val isSingleCarrier: Flow<Boolean> = latest { isSingleCarrier } + override val isRoaming: Flow<Boolean> = latest { isRoaming } + override val isForceHidden: Flow<Boolean> = latest { isForceHidden } + override val isAllowedDuringAirplaneMode: Flow<Boolean> = latest { + isAllowedDuringAirplaneMode + } + override val carrierNetworkChangeActive: Flow<Boolean> = latest { + carrierNetworkChangeActive + } + + private fun <T> latest(block: MobileIconInteractor.() -> Flow<T>): Flow<T> = + interactorsBySubId.flatMapLatestConflated { it[subId]?.block() ?: emptyFlow() } + } @dagger.Module object Module { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index 9a81992a6add..7f778bb029f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -256,6 +256,9 @@ public class Clock extends TextView implements if (mClockFormat != null) { mClockFormat.setTimeZone(mCalendar.getTimeZone()); } + if (mContentDescriptionFormat != null) { + mContentDescriptionFormat.setTimeZone(mCalendar.getTimeZone()); + } }); } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { final Locale newLocale = getResources().getConfiguration().locale; diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 05b2e0d1423e..b33eafcdfa84 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -414,16 +414,15 @@ constructor( } } + private suspend fun SelectedUserModel.isEligibleForLogout(): Boolean { + return withContext(backgroundDispatcher) { + selectionStatus == SelectionStatus.SELECTION_COMPLETE && + devicePolicyManager.logoutUser != null + } + } + companion object { private const val TAG = "UserRepository" @VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher" } } - -fun SelectedUserModel.isEligibleForLogout(): Boolean { - // TODO(b/206032495): should call mDevicePolicyManager.getLogoutUserId() instead of - // hardcode it to USER_SYSTEM so it properly supports headless system user mode - // (and then call mDevicePolicyManager.clearLogoutUser() after switched) - return selectionStatus == SelectionStatus.SELECTION_COMPLETE && - userInfo.id != android.os.UserHandle.USER_SYSTEM -} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt index 43d1ef478ae1..32f784f17bb7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt @@ -16,7 +16,6 @@ package com.android.systemui.volume.dialog.sliders.ui -import android.graphics.drawable.Drawable import android.view.View import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.tween @@ -29,7 +28,6 @@ import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SliderDefaults import androidx.compose.runtime.Composable @@ -43,7 +41,8 @@ import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.theme.PlatformTheme -import com.android.compose.ui.graphics.painter.DrawablePainter +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.ui.compose.Icon import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.res.R @@ -155,7 +154,7 @@ private fun VolumeDialogSlider( }, ) }, - accessibilityParams = AccessibilityParams(label = sliderStateModel.label), + accessibilityParams = AccessibilityParams(contentDescription = sliderStateModel.label), modifier = modifier.pointerInput(Unit) { coroutineScope { @@ -172,7 +171,7 @@ private fun VolumeDialogSlider( @Composable private fun BoxScope.VolumeIcon( - drawable: Drawable, + icon: Icon.Loaded, isVisible: Boolean, modifier: Modifier = Modifier, ) { @@ -182,6 +181,6 @@ private fun BoxScope.VolumeIcon( exit = fadeOut(animationSpec = tween(durationMillis = 50)), modifier = modifier.align(Alignment.Center).size(40.dp).padding(10.dp), ) { - Icon(painter = DrawablePainter(drawable), contentDescription = null) + Icon(icon) } } 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 ef147c741bec..3712276488ff 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 @@ -18,32 +18,36 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel import android.annotation.SuppressLint import android.content.Context -import android.graphics.drawable.Drawable import android.media.AudioManager import androidx.annotation.DrawableRes import com.android.settingslib.R as SettingsR import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.RingerMode +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.res.R import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.withContext @SuppressLint("UseCompatLoadingForDrawables") class VolumeDialogSliderIconProvider @Inject constructor( private val context: Context, + @UiBackground private val uiBackgroundContext: CoroutineContext, private val zenModeInteractor: ZenModeInteractor, private val audioVolumeInteractor: AudioVolumeInteractor, ) { - fun getAudioSharingIcon(isMuted: Boolean): Flow<Drawable> { + fun getAudioSharingIcon(isMuted: Boolean): Flow<Icon.Loaded> { return flow { val iconRes = if (isMuted) { @@ -51,11 +55,12 @@ constructor( } else { R.drawable.ic_volume_media_bt } - emit(context.getDrawable(iconRes)!!) + val drawable = withContext(uiBackgroundContext) { context.getDrawable(iconRes)!! } + emit(Icon.Loaded(drawable = drawable, contentDescription = null, res = iconRes)) } } - fun getCastIcon(isMuted: Boolean): Flow<Drawable> { + fun getCastIcon(isMuted: Boolean): Flow<Icon.Loaded> { return flow { val iconRes = if (isMuted) { @@ -63,7 +68,8 @@ constructor( } else { SettingsR.drawable.ic_volume_remote } - emit(context.getDrawable(iconRes)!!) + val drawable = withContext(uiBackgroundContext) { context.getDrawable(iconRes)!! } + emit(Icon.Loaded(drawable = drawable, contentDescription = null, res = iconRes)) } } @@ -74,15 +80,18 @@ constructor( levelMax: Int, isMuted: Boolean, isRoutedToBluetooth: Boolean, - ): Flow<Drawable> { + ): Flow<Icon.Loaded> { return combine( zenModeInteractor.activeModesBlockingStream(stream), ringerModeForStream(stream), ) { activeModesBlockingStream, ringerMode -> if (activeModesBlockingStream?.mainMode?.icon != null) { - return@combine activeModesBlockingStream.mainMode.icon.drawable + Icon.Loaded( + drawable = activeModesBlockingStream.mainMode.icon.drawable, + contentDescription = null, + ) } else { - context.getDrawable( + val iconRes = getIconRes( stream, level, @@ -92,7 +101,8 @@ constructor( isRoutedToBluetooth, ringerMode, ) - )!! + val drawable = withContext(uiBackgroundContext) { context.getDrawable(iconRes)!! } + Icon.Loaded(drawable = drawable, contentDescription = null, res = iconRes) } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt index 88a061f3813c..ed59598d97d0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt @@ -17,7 +17,7 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel import android.content.Context -import android.graphics.drawable.Drawable +import com.android.systemui.common.shared.model.Icon import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel import com.android.systemui.volume.dialog.shared.model.streamLabel @@ -25,14 +25,14 @@ data class VolumeDialogSliderStateModel( val value: Float, val isDisabled: Boolean, val valueRange: ClosedFloatingPointRange<Float>, - val icon: Drawable, + val icon: Icon.Loaded, val label: String, ) fun VolumeDialogStreamModel.toStateModel( context: Context, isDisabled: Boolean, - icon: Drawable, + icon: Icon.Loaded, ): VolumeDialogSliderStateModel { return VolumeDialogSliderStateModel( value = level.toFloat(), diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt index f6aa189eb571..faf0abd4cabd 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt @@ -108,11 +108,9 @@ constructor( isMuted = isMuted, isRoutedToBluetooth = routedToBluetooth, ) - is VolumeDialogSliderType.RemoteMediaStream -> { volumeDialogSliderIconProvider.getCastIcon(isMuted) } - is VolumeDialogSliderType.AudioSharingStream -> { volumeDialogSliderIconProvider.getAudioSharingIcon(isMuted) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt index 3d98ebacc7ca..f6452679cbf4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt @@ -16,9 +16,11 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel +import android.content.Context import com.android.internal.logging.UiEventLogger import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.res.R @@ -28,6 +30,7 @@ import com.android.systemui.volume.panel.ui.VolumePanelUiEvent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlin.coroutines.CoroutineContext import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -39,11 +42,14 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext class AudioSharingStreamSliderViewModel @AssistedInject constructor( + private val context: Context, @Assisted private val coroutineScope: CoroutineScope, + @UiBackground private val uiBackgroundContext: CoroutineContext, private val audioSharingInteractor: AudioSharingInteractor, private val uiEventLogger: UiEventLogger, private val hapticsViewModelFactory: SliderHapticsViewModel.Factory, @@ -51,6 +57,12 @@ constructor( ) : SliderViewModel { private val volumeChanges = MutableStateFlow<Int?>(null) + private val audioSharingIcon = + Icon.Loaded( + drawable = context.getDrawable(R.drawable.ic_volume_media_bt)!!, + contentDescription = null, + res = R.drawable.ic_volume_media_bt, + ) override val slider: StateFlow<SliderState> = combine( audioSharingInteractor.volume.distinctUntilChanged().onEach { @@ -62,16 +74,17 @@ constructor( if (volume == null) { SliderState.Empty } else { - - State( - value = volume.toFloat(), - valueRange = - audioSharingInteractor.volumeMin.toFloat()..audioSharingInteractor - .volumeMax - .toFloat(), - icon = Icon.Resource(R.drawable.ic_volume_media_bt, null), - label = deviceName, - ) + withContext(uiBackgroundContext) { + State( + value = volume.toFloat(), + valueRange = + audioSharingInteractor.volumeMin.toFloat()..audioSharingInteractor + .volumeMax + .toFloat(), + icon = audioSharingIcon, + label = deviceName, + ) + } } } .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty) @@ -107,7 +120,7 @@ constructor( private data class State( override val value: Float, override val valueRange: ClosedFloatingPointRange<Float>, - override val icon: Icon, + override val icon: Icon.Loaded?, override val label: String, ) : SliderState { override val hapticFilter: SliderHapticFeedbackFilter @@ -116,7 +129,7 @@ constructor( override val isEnabled: Boolean get() = true - override val a11yStep: Float + override val step: Float get() = 1f override val disabledMessage: String? @@ -125,6 +138,9 @@ constructor( override val isMutable: Boolean get() = false + override val a11yContentDescription: String + get() = label + override val a11yClickDescription: String? get() = null diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt index 9d32285fecb3..9fe0ad42cdba 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel import android.content.Context import android.media.AudioManager import android.util.Log +import androidx.annotation.DrawableRes import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.CachedBluetoothDevice @@ -28,6 +29,7 @@ import com.android.settingslib.volume.shared.model.AudioStreamModel import com.android.settingslib.volume.shared.model.RingerMode import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.modes.shared.ModesUiIcons @@ -40,18 +42,21 @@ import com.android.systemui.volume.panel.ui.VolumePanelUiEvent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlin.coroutines.CoroutineContext import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext /** Models a particular slider state. */ class AudioStreamSliderViewModel @@ -59,10 +64,11 @@ class AudioStreamSliderViewModel constructor( @Assisted private val audioStreamWrapper: FactoryAudioStreamWrapper, @Assisted private val coroutineScope: CoroutineScope, + @UiBackground private val uiBackgroundContext: CoroutineContext, private val context: Context, private val audioVolumeInteractor: AudioVolumeInteractor, private val zenModeInteractor: ZenModeInteractor, - private val audioSharingInteractor: AudioSharingInteractor, + audioSharingInteractor: AudioSharingInteractor, private val uiEventLogger: UiEventLogger, private val volumePanelLogger: VolumePanelLogger, private val hapticsViewModelFactory: SliderHapticsViewModel.Factory, @@ -148,57 +154,69 @@ constructor( null } - private fun AudioStreamModel.toState( + private suspend fun AudioStreamModel.toState( isEnabled: Boolean, ringerMode: RingerMode, disabledMessage: String?, inAudioSharing: Boolean, primaryDevice: CachedBluetoothDevice?, - ): State { - val label = getLabel(inAudioSharing, primaryDevice) - val icon = getIcon(ringerMode, inAudioSharing) - return State( - value = volume.toFloat(), - valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(), - hapticFilter = createHapticFilter(ringerMode), - icon = icon, - label = label, - disabledMessage = disabledMessage, - isEnabled = isEnabled, - a11yStep = volumeRange.step.toFloat(), - a11yClickDescription = - if (isAffectedByMute) { - context.getString( - if (isMuted) { - R.string.volume_panel_hint_unmute - } else { - R.string.volume_panel_hint_mute - }, - label, - ) - } else { - null - }, - a11yStateDescription = - if (isMuted) { - context.getString( - if (isAffectedByRingerMode) { - if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { - R.string.volume_panel_hint_vibrate + ): State = + withContext(uiBackgroundContext) { + val label = getLabel(inAudioSharing, primaryDevice) + State( + value = volume.toFloat(), + valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(), + hapticFilter = createHapticFilter(ringerMode), + icon = getIcon(ringerMode, inAudioSharing), + label = label, + disabledMessage = disabledMessage, + isEnabled = isEnabled, + step = volumeRange.step.toFloat(), + a11yContentDescription = + if (isEnabled) { + label + } else { + disabledMessage?.let { + context.getString( + R.string.volume_slider_disabled_message_template, + label, + disabledMessage, + ) + } ?: label + }, + a11yClickDescription = + if (isAffectedByMute) { + context.getString( + if (isMuted) { + R.string.volume_panel_hint_unmute + } else { + R.string.volume_panel_hint_mute + }, + label, + ) + } else { + null + }, + a11yStateDescription = + if (isMuted) { + context.getString( + if (isAffectedByRingerMode) { + if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { + R.string.volume_panel_hint_vibrate + } else { + R.string.volume_panel_hint_muted + } } else { R.string.volume_panel_hint_muted } - } else { - R.string.volume_panel_hint_muted - } - ) - } else { - null - }, - audioStreamModel = this, - isMutable = isAffectedByMute, - ) - } + ) + } else { + null + }, + audioStreamModel = this@toState, + isMutable = isAffectedByMute, + ) + } private fun AudioStreamModel.createHapticFilter( ringerMode: RingerMode @@ -220,12 +238,14 @@ constructor( flowOf(context.getString(R.string.stream_notification_unavailable)) } else { if (zenModeInteractor.canBeBlockedByZenMode(audioStream)) { - zenModeInteractor.activeModesBlockingStream(audioStream).map { blockingZenModes - -> - blockingZenModes.mainMode?.name?.let { - context.getString(R.string.stream_unavailable_by_modes, it) - } ?: context.getString(R.string.stream_unavailable_by_unknown) - } + zenModeInteractor + .activeModesBlockingStream(audioStream) + .map { blockingZenModes -> + blockingZenModes.mainMode?.name?.let { + context.getString(R.string.stream_unavailable_by_modes, it) + } ?: context.getString(R.string.stream_unavailable_by_unknown) + } + .distinctUntilChanged() } else { flowOf(context.getString(R.string.stream_unavailable_by_unknown)) } @@ -256,8 +276,11 @@ constructor( ?: error("No label for the stream: $audioStream") } - private fun AudioStreamModel.getIcon(ringerMode: RingerMode, inAudioSharing: Boolean): Icon { - val iconRes = + private fun AudioStreamModel.getIcon( + ringerMode: RingerMode, + inAudioSharing: Boolean, + ): Icon.Loaded { + val iconResource: Int = if (isMuted) { if (isAffectedByRingerMode) { if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { @@ -272,14 +295,21 @@ constructor( inAudioSharing ) { R.drawable.ic_volume_media_bt_mute - } else R.drawable.ic_volume_off + } else { + R.drawable.ic_volume_off + } } } else { getIconByStream(audioStream, inAudioSharing) } - return Icon.Resource(iconRes, null) + return Icon.Loaded( + drawable = context.getDrawable(iconResource)!!, + contentDescription = null, + res = iconResource, + ) } + @DrawableRes private fun getIconByStream(audioStream: AudioStream, inAudioSharing: Boolean): Int = when (audioStream.value) { AudioManager.STREAM_MUSIC -> @@ -302,14 +332,15 @@ constructor( private data class State( override val value: Float, override val valueRange: ClosedFloatingPointRange<Float>, + override val step: Float, override val hapticFilter: SliderHapticFeedbackFilter, - override val icon: Icon, + override val icon: Icon.Loaded?, override val label: String, override val disabledMessage: String?, override val isEnabled: Boolean, - override val a11yStep: Float, override val a11yClickDescription: String?, override val a11yStateDescription: String?, + override val a11yContentDescription: String, override val isMutable: Boolean, val audioStreamModel: AudioStreamModel, ) : SliderState diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt index a6c809186ca5..01810f9aafc3 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt @@ -21,6 +21,7 @@ import android.media.session.MediaController.PlaybackInfo import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.res.R @@ -30,30 +31,40 @@ import com.android.systemui.volume.panel.shared.VolumePanelLogger import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlin.coroutines.CoroutineContext import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext class CastVolumeSliderViewModel @AssistedInject constructor( @Assisted private val session: MediaDeviceSession, @Assisted private val coroutineScope: CoroutineScope, + @UiBackground private val uiBackgroundContext: CoroutineContext, private val context: Context, private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, private val hapticsViewModelFactory: SliderHapticsViewModel.Factory, private val volumePanelLogger: VolumePanelLogger, ) : SliderViewModel { + private val castLabel = context.getString(R.string.media_device_cast) + private val castIcon = + Icon.Loaded( + drawable = context.getDrawable(R.drawable.ic_cast)!!, + contentDescription = null, + res = R.drawable.ic_cast, + ) override val slider: StateFlow<SliderState> = mediaDeviceSessionInteractor .playbackInfo(session) .mapNotNull { volumePanelLogger.onVolumeUpdateReceived(session.sessionToken, it.currentVolume) - it.getCurrentState() + withContext(uiBackgroundContext) { it.getCurrentState() } } .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty) @@ -83,20 +94,20 @@ constructor( return State( value = currentVolume.toFloat(), valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(), - icon = Icon.Resource(R.drawable.ic_cast, null), - label = context.getString(R.string.media_device_cast), + icon = castIcon, + label = castLabel, isEnabled = true, - a11yStep = 1f, + step = 1f, ) } private data class State( override val value: Float, override val valueRange: ClosedFloatingPointRange<Float>, - override val icon: Icon, + override val icon: Icon.Loaded?, override val label: String, override val isEnabled: Boolean, - override val a11yStep: Float, + override val step: Float, ) : SliderState { override val hapticFilter: SliderHapticFeedbackFilter get() = SliderHapticFeedbackFilter() @@ -107,6 +118,9 @@ constructor( override val isMutable: Boolean get() = false + override val a11yContentDescription: String + get() = label + override val a11yClickDescription: String? get() = null diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt index 4bc237bd36f5..b1d183404a9f 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt @@ -27,18 +27,17 @@ import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter sealed interface SliderState { val value: Float val valueRange: ClosedFloatingPointRange<Float> + val step: Float val hapticFilter: SliderHapticFeedbackFilter - val icon: Icon? + // Force preloaded icon + val icon: Icon.Loaded? val isEnabled: Boolean val label: String - /** - * A11y slider controls works by adjusting one step up or down. The default slider step isn't - * enough to trigger rounding to the correct value. - */ - val a11yStep: Float + val a11yClickDescription: String? val a11yStateDescription: String? + val a11yContentDescription: String val disabledMessage: String? val isMutable: Boolean @@ -46,12 +45,13 @@ sealed interface SliderState { override val value: Float = 0f override val valueRange: ClosedFloatingPointRange<Float> = 0f..1f override val hapticFilter = SliderHapticFeedbackFilter() - override val icon: Icon? = null + override val icon: Icon.Loaded? = null override val label: String = "" override val disabledMessage: String? = null - override val a11yStep: Float = 0f + override val step: Float = 0f override val a11yClickDescription: String? = null override val a11yStateDescription: String? = null + override val a11yContentDescription: String = label override val isEnabled: Boolean = true override val isMutable: Boolean = false } diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt index f6582a005035..502b311f7b40 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt @@ -40,7 +40,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.ProgressBarRangeInfo import androidx.compose.ui.semantics.SemanticsPropertyReceiver import androidx.compose.ui.semantics.clearAndSetSemantics @@ -52,7 +51,6 @@ import androidx.compose.ui.semantics.stateDescription import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.lifecycle.rememberViewModel -import com.android.systemui.res.R import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider import kotlin.math.round import kotlinx.coroutines.Job @@ -108,7 +106,8 @@ fun Slider( } } val semantics = - accessibilityParams.createSemantics( + createSemantics( + accessibilityParams, animatable.targetValue, valueRange, valueChange, @@ -167,24 +166,18 @@ private fun snapValue( return Math.round(coercedValue / stepDistance) * stepDistance } -@Composable -private fun AccessibilityParams.createSemantics( +private fun createSemantics( + params: AccessibilityParams, value: Float, valueRange: ClosedFloatingPointRange<Float>, onValueChanged: (Float) -> Unit, isEnabled: Boolean, stepDistance: Float, ): SemanticsPropertyReceiver.() -> Unit { - val semanticsContentDescription = - disabledMessage - ?.takeIf { !isEnabled } - ?.let { message -> - stringResource(R.string.volume_slider_disabled_message_template, label, message) - } ?: label return { - contentDescription = semanticsContentDescription + contentDescription = params.contentDescription if (isEnabled) { - currentStateDescription?.let { stateDescription = it } + params.stateDescription?.let { stateDescription = it } progressBarRangeInfo = ProgressBarRangeInfo(value, valueRange) } else { disabled() @@ -253,9 +246,8 @@ private fun Haptics.createViewModel( } data class AccessibilityParams( - val label: String, - val currentStateDescription: String? = null, - val disabledMessage: String? = null, + val contentDescription: String, + val stateDescription: String? = null, ) sealed interface Haptics { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java index 146488b523ad..108f3ae86f9b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java @@ -69,4 +69,25 @@ public class MenuViewAppearanceTest extends SysuiTestCase { assertThat(end_y).isEqualTo(end_y); } + + @Test + public void avoidVerticalDisplayCutout_doesNotExceedTopBounds() { + final int y = DRAGGABLE_BOUNDS.top - 100; + + final float end_y = MenuViewAppearance.avoidVerticalDisplayCutout( + y, MENU_HEIGHT, DRAGGABLE_BOUNDS, new Rect(0, 5, 0, 6)); + + assertThat(end_y).isGreaterThan(DRAGGABLE_BOUNDS.top); + } + + + @Test + public void avoidVerticalDisplayCutout_doesNotExceedBottomBounds() { + final int y = DRAGGABLE_BOUNDS.bottom + 100; + + final float end_y = MenuViewAppearance.avoidVerticalDisplayCutout( + y, MENU_HEIGHT, DRAGGABLE_BOUNDS, new Rect(0, 5, 0, 6)); + + assertThat(end_y).isLessThan(DRAGGABLE_BOUNDS.bottom); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt index cf8278eb8ac6..82082cc778b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 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. @@ -38,9 +38,13 @@ import com.android.internal.widget.NotificationExpandButton import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.FeedbackIcon import com.android.systemui.statusbar.notification.collection.EntryAdapter +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactory import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.msgStyleBubbleableFullPerson +import com.android.systemui.statusbar.notification.people.peopleNotificationIdentifier import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.shared.NotificationBundleUi +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -51,7 +55,6 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyInt @@ -67,10 +70,12 @@ import org.mockito.MockitoAnnotations.initMocks @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class NotificationContentViewTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private val factory: EntryAdapterFactory = kosmos.entryAdapterFactory private lateinit var row: ExpandableNotificationRow private lateinit var fakeParent: ViewGroup - @Mock private lateinit var mPeopleNotificationIdentifier: PeopleNotificationIdentifier private val testableResources = mContext.getOrCreateTestableResources() private val contractedHeight = @@ -82,24 +87,19 @@ class NotificationContentViewTest : SysuiTestCase() { fun setup() { initMocks(this) fakeParent = - spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE }) - val mockEntry = createMockNotificationEntry() - val mockEntryAdapter = createMockNotificationEntryAdapter() + spy(FrameLayout(mContext, /* attrs= */ null)).also { it.visibility = View.GONE } + val entry = kosmos.msgStyleBubbleableFullPerson + val mockEntryAdapter = factory.create(entry) row = spy( when (NotificationBundleUi.isEnabled) { true -> { - ExpandableNotificationRow( - mContext, - /* attrs= */ null, - UserHandle.CURRENT - ).apply { - entryAdapter = mockEntryAdapter - } + ExpandableNotificationRow(mContext, /* attrs= */ null, UserHandle.CURRENT) + .apply { entryAdapter = mockEntryAdapter } } false -> { - ExpandableNotificationRow(mContext, /* attrs= */ null, mockEntry).apply { - entryLegacy = mockEntry + ExpandableNotificationRow(mContext, /* attrs= */ null, entry).apply { + entryLegacy = entry } } } @@ -406,11 +406,11 @@ class NotificationContentViewTest : SysuiTestCase() { fun setExpandedChild_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() { // Given: bottom margin of actionListMarginTarget is notificationContentMargin // Bubble button should not be shown for the given NotificationEntry - val mockNotificationEntry = createMockNotificationEntry() + val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson val mockContainingNotification = createMockContainingNotification(mockNotificationEntry) val actionListMarginTarget = spy(createLinearLayoutWithBottomMargin(notificationContentMargin)) - val mockExpandedChild = createMockExpandedChild(mockNotificationEntry) + val mockExpandedChild = createMockExpandedChild() whenever( mockExpandedChild.findViewById<LinearLayout>( R.id.notification_action_list_margin_target @@ -434,11 +434,11 @@ class NotificationContentViewTest : SysuiTestCase() { fun setExpandedChild_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() { // Given: bottom margin of actionListMarginTarget is notificationContentMargin // Bubble button should be shown for the given NotificationEntry - val mockNotificationEntry = createMockNotificationEntry() + val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson val mockContainingNotification = createMockContainingNotification(mockNotificationEntry) val actionListMarginTarget = spy(createLinearLayoutWithBottomMargin(notificationContentMargin)) - val mockExpandedChild = createMockExpandedChild(mockNotificationEntry) + val mockExpandedChild = createMockExpandedChild() whenever( mockExpandedChild.findViewById<LinearLayout>( R.id.notification_action_list_margin_target @@ -463,11 +463,11 @@ class NotificationContentViewTest : SysuiTestCase() { @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES) fun onNotificationUpdated_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() { // Given: bottom margin of actionListMarginTarget is notificationContentMargin - val mockNotificationEntry = createMockNotificationEntry() + val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson val mockContainingNotification = createMockContainingNotification(mockNotificationEntry) val actionListMarginTarget = spy(createLinearLayoutWithBottomMargin(notificationContentMargin)) - val mockExpandedChild = createMockExpandedChild(mockNotificationEntry) + val mockExpandedChild = createMockExpandedChild() whenever( mockExpandedChild.findViewById<LinearLayout>( R.id.notification_action_list_margin_target @@ -482,7 +482,7 @@ class NotificationContentViewTest : SysuiTestCase() { // When: call NotificationContentView.onNotificationUpdated() to update the // NotificationEntry, which should not show bubble button - view.onNotificationUpdated(createMockNotificationEntry()) + view.onNotificationUpdated(kosmos.msgStyleBubbleableFullPerson) // Then: bottom margin of actionListMarginTarget should not change, still be 20 assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget)) @@ -492,11 +492,11 @@ class NotificationContentViewTest : SysuiTestCase() { @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES) fun onNotificationUpdated_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() { // Given: bottom margin of actionListMarginTarget is notificationContentMargin - val mockNotificationEntry = createMockNotificationEntry() + val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson val mockContainingNotification = createMockContainingNotification(mockNotificationEntry) val actionListMarginTarget = spy(createLinearLayoutWithBottomMargin(notificationContentMargin)) - val mockExpandedChild = createMockExpandedChild(mockNotificationEntry) + val mockExpandedChild = createMockExpandedChild() whenever( mockExpandedChild.findViewById<LinearLayout>( R.id.notification_action_list_margin_target @@ -510,7 +510,7 @@ class NotificationContentViewTest : SysuiTestCase() { // When: call NotificationContentView.onNotificationUpdated() to update the // NotificationEntry, which should show bubble button - view.onNotificationUpdated(createMockNotificationEntry(/*true*/ )) + view.onNotificationUpdated(kosmos.msgStyleBubbleableFullPerson) // Then: no bubble yet assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget)) @@ -615,15 +615,17 @@ class NotificationContentViewTest : SysuiTestCase() { private fun createMockContainingNotification(notificationEntry: NotificationEntry) = mock<ExpandableNotificationRow>().apply { - whenever(this.entry).thenReturn(notificationEntry) + if (!NotificationBundleUi.isEnabled) { + whenever(this.entryLegacy).thenReturn(notificationEntry) + } whenever(this.context).thenReturn(mContext) whenever(this.bubbleClickListener).thenReturn(View.OnClickListener {}) - whenever(this.entryAdapter).thenReturn(createMockNotificationEntryAdapter()) + whenever(this.entryAdapter).thenReturn(factory.create(notificationEntry)) } private fun createMockNotificationEntry() = mock<NotificationEntry>().apply { - whenever(mPeopleNotificationIdentifier.getPeopleNotificationType(this)) + whenever(kosmos.peopleNotificationIdentifier.getPeopleNotificationType(this)) .thenReturn(PeopleNotificationIdentifier.TYPE_FULL_PERSON) whenever(this.bubbleMetadata).thenReturn(mock()) val sbnMock: StatusBarNotification = mock() @@ -632,7 +634,8 @@ class NotificationContentViewTest : SysuiTestCase() { whenever(sbnMock.user).thenReturn(userMock) } - private fun createMockNotificationEntryAdapter() = mock<EntryAdapter>() + private fun createMockNotificationEntryAdapter() = + mock<EntryAdapter>() private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout { val outerLayout = LinearLayout(mContext) @@ -643,7 +646,7 @@ class NotificationContentViewTest : SysuiTestCase() { return innerLayout } - private fun createMockExpandedChild(notificationEntry: NotificationEntry) = + private fun createMockExpandedChild() = spy(createViewWithHeight(expandedHeight)).apply { whenever(this.findViewById<ImageView>(R.id.bubble_button)).thenReturn(mock()) whenever(this.findViewById<View>(R.id.actions_container)).thenReturn(mock()) @@ -664,9 +667,16 @@ class NotificationContentViewTest : SysuiTestCase() { val height = if (isSystemExpanded) expandedHeight else contractedHeight doReturn(height).whenever(row).intrinsicHeight - return spy(NotificationContentView(mContext, /* attrs= */ null)) + return NotificationContentView(mContext, /* attrs= */ null) .apply { - initialize(mPeopleNotificationIdentifier, mock(), mock(), mock(), mock(), mock()) + initialize( + kosmos.peopleNotificationIdentifier, + mock(), + mock(), + mock(), + mock(), + mock(), + ) setContainingNotification(row) setHeights( /* smallHeight= */ contractedHeight, 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 8fb2a245921a..6a9a485151de 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 @@ -734,6 +734,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); NotificationEntry entry = mock(NotificationEntry.class); when(row.getEntry()).thenReturn(entry); + when(row.getEntryLegacy()).thenReturn(entry); when(entry.isAmbient()).thenReturn(false); EntryAdapter entryAdapter = mock(EntryAdapter.class); when(entryAdapter.isAmbient()).thenReturn(false); @@ -753,6 +754,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); NotificationEntry entry = mock(NotificationEntry.class); when(row.getEntry()).thenReturn(entry); + when(row.getEntryLegacy()).thenReturn(entry); when(entry.isAmbient()).thenReturn(true); EntryAdapter entryAdapter = mock(EntryAdapter.class); when(entryAdapter.isAmbient()).thenReturn(true); @@ -772,6 +774,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); NotificationEntry entry = mock(NotificationEntry.class); when(row.getEntry()).thenReturn(entry); + when(row.getEntryLegacy()).thenReturn(entry); when(entry.isAmbient()).thenReturn(false); EntryAdapter entryAdapter = mock(EntryAdapter.class); when(entryAdapter.isAmbient()).thenReturn(false); @@ -1384,6 +1387,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { NotificationEntry entry = mock(NotificationEntry.class); when(entry.isSeenInShade()).thenReturn(true); ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + when(row.getEntryLegacy()).thenReturn(entry); when(row.getEntry()).thenReturn(entry); // WHEN we generate an add event @@ -1440,6 +1444,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { NotificationEntry entry = mock(NotificationEntry.class); when(row.canViewBeCleared()).thenReturn(true); when(row.getEntry()).thenReturn(entry); + when(row.getEntryLegacy()).thenReturn(entry); when(entry.isClearable()).thenReturn(true); EntryAdapter entryAdapter = mock(EntryAdapter.class); when(entryAdapter.isClearable()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index a3616d20e11f..a7f3fdcb517e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -28,8 +28,8 @@ import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED; import static android.provider.Settings.Global.HEADS_UP_ON; import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE; -import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION; import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT; +import static com.android.systemui.shared.Flags.FLAG_AMBIENT_AOD; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.phone.CentralSurfaces.MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU; @@ -242,7 +242,7 @@ import javax.inject.Provider; @SmallTest @RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) -@EnableFlags(FLAG_LIGHT_REVEAL_MIGRATION) +@EnableFlags(FLAG_AMBIENT_AOD) public class CentralSurfacesImplTest extends SysuiTestCase { private static final DeviceState FOLD_STATE_FOLDED = new DeviceState( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index 15cb95a99967..846db6389d0c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -15,6 +15,7 @@ */ package com.android.systemui; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -49,11 +50,13 @@ import com.android.systemui.log.LogWtfHandlerRule; import org.junit.After; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.mockito.Mockito; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.annotation.Retention; @@ -190,6 +193,7 @@ public abstract class SysuiTestCase { @Before public void SysuiSetup() throws Exception { + assertTempFilesAreCreatable(); ProtoLog.REQUIRE_PROTOLOGTOOL = false; mSysuiDependency = new SysuiTestDependency(mContext, shouldFailOnLeakedReceiver()); mDependency = mSysuiDependency.install(); @@ -211,6 +215,28 @@ public abstract class SysuiTestCase { } } + private static Boolean sCanCreateTempFiles = null; + + private static void assertTempFilesAreCreatable() { + // TODO(b/391948934): hopefully remove this hack + if (sCanCreateTempFiles == null) { + try { + File tempFile = File.createTempFile("confirm_temp_file_createable", "txt"); + sCanCreateTempFiles = true; + assertTrue(tempFile.delete()); + } catch (IOException e) { + sCanCreateTempFiles = false; + throw new RuntimeException(e); + } + } + if (!sCanCreateTempFiles) { + Assert.fail( + "Cannot create temp files, so mockito will probably fail (b/391948934). Temp" + + " folder should be: " + + System.getProperty("java.io.tmpdir")); + } + } + protected boolean shouldFailOnLeakedReceiver() { return false; } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt index 338e4bec7aa2..122e6a507cbf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.display.data.repository import android.view.Display +import com.android.app.displaylib.DisplayRepository.PendingDisplay import com.android.systemui.dagger.SysUISingleton import com.android.systemui.util.mockito.mock import dagger.Binds @@ -26,7 +27,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import com.android.app.displaylib.DisplayRepository.PendingDisplay import org.mockito.Mockito.`when` as whenever /** Creates a mock display. */ @@ -50,8 +50,7 @@ fun createPendingDisplay(id: Int = 0): PendingDisplay = class FakeDisplayRepository @Inject constructor() : DisplayRepository { private val flow = MutableStateFlow<Set<Display>>(emptySet()) private val displayIdFlow = MutableStateFlow<Set<Int>>(emptySet()) - private val pendingDisplayFlow = - MutableSharedFlow<PendingDisplay?>(replay = 1) + private val pendingDisplayFlow = MutableSharedFlow<PendingDisplay?>(replay = 1) private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 0) private val displayRemovalEventFlow = MutableSharedFlow<Int>(replay = 0) private val displayIdsWithSystemDecorationsFlow = MutableStateFlow<Set<Int>>(emptySet()) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt index 5ab3b3de49f4..4b516e9c74bc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt @@ -16,6 +16,8 @@ package com.android.systemui.display.data.repository +import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown +import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index 623989ec5809..c80d7386f67a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -81,6 +81,7 @@ import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlag import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor +import com.android.systemui.statusbar.notification.row.entryAdapterFactory import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor import com.android.systemui.statusbar.phone.fakeAutoHideControllerStore @@ -206,4 +207,5 @@ class KosmosJavaAdapter() { val displayTracker by lazy { kosmos.displayTracker } val fakeShadeDisplaysRepository by lazy { kosmos.fakeShadeDisplaysRepository } val sysUIStateDispatcher by lazy { kosmos.sysUIStateDispatcher } + val entryAdapterFactory by lazy { kosmos.entryAdapterFactory } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationEntryBuilderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilderKosmos.kt index 59f5ecd2563f..00c6c9445fe7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationEntryBuilderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilderKosmos.kt @@ -14,18 +14,25 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification +package com.android.systemui.statusbar.notification.collection import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.PendingIntent import android.app.Person import android.content.Intent import android.content.applicationContext +import android.graphics.Bitmap import android.graphics.drawable.Icon +import com.android.systemui.activity.EmptyTestActivity import com.android.systemui.kosmos.Kosmos -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.res.R import com.android.systemui.statusbar.notification.icon.IconPack +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON import com.android.systemui.statusbar.notification.promoted.setPromotedContent import org.mockito.kotlin.mock @@ -40,6 +47,11 @@ fun Kosmos.setIconPackWithMockIconViews(entry: NotificationEntry) { ) } +fun Kosmos.buildPromotedOngoingEntry( + block: NotificationEntryBuilder.() -> Unit = {} +): NotificationEntry = + buildNotificationEntry(tag = "ron", promoted = true, style = null, block = block) + fun Kosmos.buildOngoingCallEntry( promoted: Boolean = false, block: NotificationEntryBuilder.() -> Unit = {}, @@ -51,11 +63,6 @@ fun Kosmos.buildOngoingCallEntry( block = block, ) -fun Kosmos.buildPromotedOngoingEntry( - block: NotificationEntryBuilder.() -> Unit = {} -): NotificationEntry = - buildNotificationEntry(tag = "ron", promoted = true, style = null, block = block) - fun Kosmos.buildNotificationEntry( tag: String? = null, promoted: Boolean = false, @@ -88,3 +95,49 @@ private fun Kosmos.makeOngoingCallStyle(): Notification.CallStyle { val person = Person.Builder().setName("person").build() return Notification.CallStyle.forOngoingCall(person, pendingIntent) } + +private fun Kosmos.makeMessagingStyleNotification(): Notification.Builder { + val personIcon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)) + val person = Person.Builder().setIcon(personIcon).setName("Person").build() + val message = Notification.MessagingStyle.Message("Message!", 4323, person) + val bubbleIntent = + PendingIntent.getActivity( + applicationContext, + 0, + Intent(applicationContext, EmptyTestActivity::class.java), + PendingIntent.FLAG_MUTABLE, + ) + + return Notification.Builder(applicationContext, "channelId") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text") + .setStyle(Notification.MessagingStyle(person).addMessage(message)) + .setBubbleMetadata( + Notification.BubbleMetadata.Builder( + bubbleIntent, + Icon.createWithResource(applicationContext, R.drawable.android), + ) + .setDeleteIntent(mock<PendingIntent>()) + .setDesiredHeight(314) + .setAutoExpandBubble(false) + .build() + ) +} + +fun Kosmos.makeEntryOfPeopleType(@PeopleNotificationType type: Int): NotificationEntryBuilder { + val channel = NotificationChannel("messages", "messages", IMPORTANCE_DEFAULT) + channel.isImportantConversation = (type == TYPE_IMPORTANT_PERSON) + channel.setConversationId("parent", "convo") + + val entry = + NotificationEntryBuilder().apply { + updateRanking { + it.setIsConversation(type != TYPE_NON_PERSON) + it.setShortcutInfo(if (type >= TYPE_FULL_PERSON) mock() else null) + it.setChannel(channel) + } + setNotification(makeMessagingStyleNotification().build()) + } + return entry +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryKosmos.kt new file mode 100644 index 000000000000..e127a70dc07d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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.statusbar.notification.collection + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON + +val Kosmos.msgStyleBubbleableFullPerson by + Kosmos.Fixture { makeEntryOfPeopleType(TYPE_FULL_PERSON).build() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt index 09f9f1c6362e..44d7a22b6258 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel import android.content.applicationContext import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.volume.domain.interactor.audioVolumeInteractor @@ -25,6 +26,7 @@ val Kosmos.volumeDialogSliderIconProvider by Kosmos.Fixture { VolumeDialogSliderIconProvider( context = applicationContext, + uiBackgroundContext = backgroundCoroutineContext, audioVolumeInteractor = audioVolumeInteractor, zenModeInteractor = zenModeInteractor, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt index 8c8d0240f572..6e43d79295ff 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt @@ -16,9 +16,11 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel +import android.content.applicationContext import com.android.internal.logging.uiEventLogger import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext import com.android.systemui.volume.domain.interactor.audioSharingInteractor import com.android.systemui.volume.shared.volumePanelLogger import kotlinx.coroutines.CoroutineScope @@ -28,7 +30,9 @@ val Kosmos.audioSharingStreamSliderViewModelFactory by object : AudioSharingStreamSliderViewModel.Factory { override fun create(coroutineScope: CoroutineScope): AudioSharingStreamSliderViewModel { return AudioSharingStreamSliderViewModel( + applicationContext, coroutineScope, + backgroundCoroutineContext, audioSharingInteractor, uiEventLogger, sliderHapticsViewModelFactory, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt index 88c716e0ab10..47016e535ea4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt @@ -20,6 +20,7 @@ import android.content.applicationContext import com.android.internal.logging.uiEventLogger import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.volume.domain.interactor.audioSharingInteractor import com.android.systemui.volume.domain.interactor.audioVolumeInteractor @@ -37,6 +38,7 @@ val Kosmos.audioStreamSliderViewModelFactory by return AudioStreamSliderViewModel( audioStream, coroutineScope, + backgroundCoroutineContext, applicationContext, audioVolumeInteractor, zenModeInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt index 6875619d45fc..ed51e054e50c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt @@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel import android.content.applicationContext import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext import com.android.systemui.volume.mediaDeviceSessionInteractor import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession import com.android.systemui.volume.shared.volumePanelLogger @@ -34,6 +35,7 @@ val Kosmos.castVolumeSliderViewModelFactory by return CastVolumeSliderViewModel( session, coroutineScope, + backgroundCoroutineContext, applicationContext, mediaDeviceSessionInteractor, sliderHapticsViewModelFactory, diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt index b956e44e0618..90a92712bf81 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt @@ -281,7 +281,6 @@ internal class DepthTracker { }, ) } - downstreamSet.clear() } } reset() diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt index c11eb122597d..81f37022a868 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt @@ -145,7 +145,14 @@ internal class MuxDeferredNode<W, K, V>( val conn = branchNode.upstream severed.add(conn) conn.removeDownstream(downstream = branchNode.schedulable) - depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + if (conn.depthTracker.snapshotIsDirect) { + depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + } else { + depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth) + depthTracker.updateIndirectRoots( + removals = conn.depthTracker.snapshotIndirectRoots + ) + } } } @@ -156,7 +163,14 @@ internal class MuxDeferredNode<W, K, V>( val conn = branchNode.upstream severed.add(conn) conn.removeDownstream(downstream = branchNode.schedulable) - depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + if (conn.depthTracker.snapshotIsDirect) { + depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + } else { + depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth) + depthTracker.updateIndirectRoots( + removals = conn.depthTracker.snapshotIndirectRoots + ) + } } // add new @@ -343,13 +357,8 @@ private class MuxDeferredActivator<W, K, V>( val (patchesConn, needsEval) = getPatches(evalScope).activate(evalScope, downstream = muxNode.schedulable) ?: run { - // Turns out we can't connect to patches, so update our depth and - // propagate - if (muxNode.depthTracker.setIsIndirectRoot(false)) { - // TODO: schedules might not be necessary now that we're not - // parallel? - muxNode.depthTracker.schedule(evalScope.scheduler, muxNode) - } + // Turns out we can't connect to patches, so update our depth + muxNode.depthTracker.setIsIndirectRoot(false) return } muxNode.patches = patchesConn diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt index cb2c6e51df66..faef6a21d4f3 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt @@ -109,7 +109,14 @@ internal class MuxPromptNode<W, K, V>( val conn: NodeConnection<V> = branchNode.upstream severed.add(conn) conn.removeDownstream(downstream = branchNode.schedulable) - depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + if (conn.depthTracker.snapshotIsDirect) { + depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + } else { + depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth) + depthTracker.updateIndirectRoots( + removals = conn.depthTracker.snapshotIndirectRoots + ) + } } } @@ -123,7 +130,14 @@ internal class MuxPromptNode<W, K, V>( val conn: NodeConnection<V> = oldBranch.upstream severed.add(conn) conn.removeDownstream(downstream = oldBranch.schedulable) - depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + if (conn.depthTracker.snapshotIsDirect) { + depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + } else { + depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth) + depthTracker.updateIndirectRoots( + removals = conn.depthTracker.snapshotIndirectRoots + ) + } } // add new diff --git a/ravenwood/tools/hoststubgen/Android.bp b/ravenwood/tools/hoststubgen/Android.bp index 004834eed983..e605318f4a10 100644 --- a/ravenwood/tools/hoststubgen/Android.bp +++ b/ravenwood/tools/hoststubgen/Android.bp @@ -99,6 +99,7 @@ java_library_host { "ow2-asm-commons", "ow2-asm-tree", "ow2-asm-util", + "apache-commons-compress", ], } diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt index b2af7827f8c5..a62f66dd18e5 100644 --- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt @@ -16,6 +16,15 @@ package com.android.hoststubgen import java.io.PrintWriter +import java.util.zip.CRC32 +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream +import org.apache.commons.compress.archivers.zip.ZipFile + +/** + * Whether to skip compression when adding processed entries back to a zip file. + */ +private const val SKIP_COMPRESSION = false /** * Name of this executable. Set it in the main method. @@ -118,3 +127,29 @@ inline fun runMainWithBoilerplate(realMain: () -> Unit) { System.exit(if (success) 0 else 1 ) } + +/** + * Copy a single ZIP entry to the output. + */ +fun copyZipEntry( + inZip: ZipFile, + entry: ZipArchiveEntry, + out: ZipArchiveOutputStream, +) { + inZip.getRawInputStream(entry).use { out.addRawArchiveEntry(entry, it) } +} + +/** + * Add a single ZIP entry with data. + */ +fun ZipArchiveOutputStream.addBytesEntry(name: String, data: ByteArray) { + val newEntry = ZipArchiveEntry(name) + if (SKIP_COMPRESSION) { + newEntry.method = 0 + newEntry.size = data.size.toLong() + newEntry.crc = CRC32().apply { update(data) }.value + } + putArchiveEntry(newEntry) + write(data) + closeArchiveEntry() +} diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt index e2647eb13ed3..40d343ab9658 100644 --- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt @@ -18,21 +18,20 @@ package com.android.hoststubgen.asm import com.android.hoststubgen.ClassParseException import com.android.hoststubgen.InvalidJarFileException import com.android.hoststubgen.log -import org.objectweb.asm.ClassReader -import org.objectweb.asm.tree.AnnotationNode -import org.objectweb.asm.tree.ClassNode -import org.objectweb.asm.tree.FieldNode -import org.objectweb.asm.tree.MethodNode -import org.objectweb.asm.tree.TypeAnnotationNode -import java.io.BufferedInputStream import java.io.PrintWriter import java.util.Arrays import java.util.concurrent.Executors import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicReference import java.util.function.Consumer -import java.util.zip.ZipEntry -import java.util.zip.ZipFile +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry +import org.apache.commons.compress.archivers.zip.ZipFile +import org.objectweb.asm.ClassReader +import org.objectweb.asm.tree.AnnotationNode +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.FieldNode +import org.objectweb.asm.tree.MethodNode +import org.objectweb.asm.tree.TypeAnnotationNode /** * Stores all classes loaded from a jar file, in a form of [ClassNode] @@ -189,7 +188,8 @@ class ClassNodes { * Load all the classes, without code. */ fun loadClassStructures( - inJar: String, + inJar: ZipFile, + jarName: String, timeCollector: Consumer<Double>? = null, ): ClassNodes { val allClasses = ClassNodes() @@ -201,20 +201,19 @@ class ClassNodes { val exception = AtomicReference<Throwable>() // Called on a BG thread. Read a single jar entry and add it to [allClasses]. - fun parseClass(inZip: ZipFile, entry: ZipEntry) { + fun parseClass(inZip: ZipFile, entry: ZipArchiveEntry) { try { - inZip.getInputStream(entry).use { ins -> - val cr = ClassReader(BufferedInputStream(ins)) - val cn = ClassNode() - cr.accept( - cn, ClassReader.SKIP_CODE - or ClassReader.SKIP_DEBUG - or ClassReader.SKIP_FRAMES - ) - synchronized(allClasses) { - if (!allClasses.addClass(cn)) { - log.w("Duplicate class found: ${cn.name}") - } + val classBytes = inZip.getInputStream(entry).use { it.readAllBytes() } + val cr = ClassReader(classBytes) + val cn = ClassNode() + cr.accept( + cn, ClassReader.SKIP_CODE + or ClassReader.SKIP_DEBUG + or ClassReader.SKIP_FRAMES + ) + synchronized(allClasses) { + if (!allClasses.addClass(cn)) { + log.w("Duplicate class found: ${cn.name}") } } } catch (e: Throwable) { @@ -224,35 +223,30 @@ class ClassNodes { } // Actually open the jar and read it on worker threads. - val time = log.iTime("Reading class structure from $inJar") { + val time = log.iTime("Reading class structure from $jarName") { log.withIndent { - ZipFile(inJar).use { inZip -> - val inEntries = inZip.entries() - - while (inEntries.hasMoreElements()) { - val entry = inEntries.nextElement() - - if (entry.name.endsWith(".class")) { - executor.submit { - parseClass(inZip, entry) - } - } else if (entry.name.endsWith(".dex")) { - // Seems like it's an ART jar file. We can't process it. - // It's a fatal error. - throw InvalidJarFileException( - "$inJar is not a desktop jar file." - + " It contains a *.dex file." - ) - } else { - // Unknown file type. Skip. + inJar.entries.asSequence().forEach { entry -> + if (entry.name.endsWith(".class")) { + executor.submit { + parseClass(inJar, entry) } + } else if (entry.name.endsWith(".dex")) { + // Seems like it's an ART jar file. We can't process it. + // It's a fatal error. + throw InvalidJarFileException( + "$jarName is not a desktop jar file." + + " It contains a *.dex file." + ) + } else { + // Unknown file type. Skip. } - // Wait for all the work to complete. (must do it before closing the zip) - log.i("Waiting for all loaders to finish...") - executor.shutdown() - executor.awaitTermination(5, TimeUnit.MINUTES) - log.i("All loaders to finished.") } + + // Wait for all the work to complete. (must do it before closing the zip) + log.i("Waiting for all loaders to finish...") + executor.shutdown() + executor.awaitTermination(5, TimeUnit.MINUTES) + log.i("All loaders to finished.") } // If any exception is detected, throw it. @@ -261,13 +255,13 @@ class ClassNodes { } if (allClasses.size == 0) { - log.w("$inJar contains no *.class files.") + log.w("$jarName contains no *.class files.") } else { - log.i("Loaded ${allClasses.size} classes from $inJar.") + log.i("Loaded ${allClasses.size} classes from $jarName.") } } timeCollector?.accept(time) return allClasses } } -}
\ No newline at end of file +} diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 333540573364..2edcb2a6c199 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -19,13 +19,11 @@ import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.dumper.ApiDumper import com.android.hoststubgen.filters.FilterPolicy import com.android.hoststubgen.filters.printAsTextPolicy -import java.io.BufferedInputStream -import java.io.BufferedOutputStream import java.io.FileOutputStream import java.io.PrintWriter -import java.util.zip.ZipEntry -import java.util.zip.ZipFile -import java.util.zip.ZipOutputStream +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream +import org.apache.commons.compress.archivers.zip.ZipFile /** * Actual main class. @@ -34,9 +32,10 @@ class HostStubGen(val options: HostStubGenOptions) { fun run() { val errors = HostStubGenErrors() val stats = HostStubGenStats() + val inJar = ZipFile(options.inJar.get) // Load all classes. - val allClasses = ClassNodes.loadClassStructures(options.inJar.get) + val allClasses = ClassNodes.loadClassStructures(inJar, options.inJar.get) // Dump the classes, if specified. options.inputJarDumpFile.ifSet { @@ -59,7 +58,7 @@ class HostStubGen(val options: HostStubGenOptions) { val processor = HostStubGenClassProcessor(options, allClasses, errors, stats) // Transform the jar. - convert( + inJar.convert( options.inJar.get, options.outJar.get, processor, @@ -88,7 +87,7 @@ class HostStubGen(val options: HostStubGenOptions) { /** * Convert a JAR file into "stub" and "impl" JAR files. */ - private fun convert( + private fun ZipFile.convert( inJar: String, outJar: String?, processor: HostStubGenClassProcessor, @@ -100,45 +99,39 @@ class HostStubGen(val options: HostStubGenOptions) { log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled") log.iTime("Transforming jar") { - var itemIndex = 0 var numItemsProcessed = 0 var numItems = -1 // == Unknown log.withIndent { - // Open the input jar file and process each entry. - ZipFile(inJar).use { inZip -> - - numItems = inZip.size() - val shardStart = numItems * shard / numShards - val shardNextStart = numItems * (shard + 1) / numShards - - maybeWithZipOutputStream(outJar) { outStream -> - val inEntries = inZip.entries() - while (inEntries.hasMoreElements()) { - val entry = inEntries.nextElement() - val inShard = (shardStart <= itemIndex) - && (itemIndex < shardNextStart) - itemIndex++ - if (!inShard) { - continue - } - convertSingleEntry(inZip, entry, outStream, processor) - numItemsProcessed++ + val entries = entries.toList() + + numItems = entries.size + val shardStart = numItems * shard / numShards + val shardNextStart = numItems * (shard + 1) / numShards + + maybeWithZipOutputStream(outJar) { outStream -> + entries.forEachIndexed { itemIndex, entry -> + val inShard = (shardStart <= itemIndex) + && (itemIndex < shardNextStart) + if (!inShard) { + return@forEachIndexed } - log.i("Converted all entries.") + convertSingleEntry(this, entry, outStream, processor) + numItemsProcessed++ } - outJar?.let { log.i("Created: $it") } + log.i("Converted all entries.") } + outJar?.let { log.i("Created: $it") } } log.i("%d / %d item(s) processed.", numItemsProcessed, numItems) } } - private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T { + private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipArchiveOutputStream?) -> T): T { if (filename == null) { return block(null) } - return ZipOutputStream(BufferedOutputStream(FileOutputStream(filename))).use(block) + return ZipArchiveOutputStream(FileOutputStream(filename).buffered()).use(block) } /** @@ -146,8 +139,8 @@ class HostStubGen(val options: HostStubGenOptions) { */ private fun convertSingleEntry( inZip: ZipFile, - entry: ZipEntry, - outStream: ZipOutputStream?, + entry: ZipArchiveEntry, + outStream: ZipArchiveOutputStream?, processor: HostStubGenClassProcessor ) { log.d("Entry: %s", entry.name) @@ -181,32 +174,12 @@ class HostStubGen(val options: HostStubGenOptions) { } /** - * Copy a single ZIP entry to the output. - */ - private fun copyZipEntry( - inZip: ZipFile, - entry: ZipEntry, - out: ZipOutputStream, - ) { - // TODO: It seems like copying entries this way is _very_ slow, - // even with out.setLevel(0). Look for other ways to do it. - - inZip.getInputStream(entry).use { ins -> - // Copy unknown entries as is to the impl out. (but not to the stub out.) - val outEntry = ZipEntry(entry.name) - out.putNextEntry(outEntry) - ins.transferTo(out) - out.closeEntry() - } - } - - /** * Convert a single class. */ private fun processSingleClass( inZip: ZipFile, - entry: ZipEntry, - outStream: ZipOutputStream?, + entry: ZipArchiveEntry, + outStream: ZipArchiveOutputStream?, processor: HostStubGenClassProcessor ) { val classInternalName = entry.name.replaceFirst("\\.class$".toRegex(), "") @@ -227,12 +200,10 @@ class HostStubGen(val options: HostStubGenOptions) { if (outStream != null) { log.v("Creating class: %s Policy: %s", classInternalName, classPolicy) log.withIndent { - BufferedInputStream(inZip.getInputStream(entry)).use { bis -> - val newEntry = ZipEntry(newName) - outStream.putNextEntry(newEntry) - val classBytecode = bis.readAllBytes() - outStream.write(processor.processClassBytecode(classBytecode)) - outStream.closeEntry() + inZip.getInputStream(entry).use { zis -> + var classBytecode = zis.readAllBytes() + classBytecode = processor.processClassBytecode(classBytecode) + outStream.addBytesEntry(newName, classBytecode) } } } diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt index 04e3bda2ba27..8e36323fd495 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt @@ -17,17 +17,17 @@ package com.android.platform.test.ravenwood.ravenizer import com.android.hoststubgen.GeneralUserErrorException import com.android.hoststubgen.HostStubGenClassProcessor +import com.android.hoststubgen.addBytesEntry import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.zipEntryNameToClassName +import com.android.hoststubgen.copyZipEntry import com.android.hoststubgen.executableName import com.android.hoststubgen.log import com.android.platform.test.ravenwood.ravenizer.adapter.RunnerRewritingAdapter -import java.io.BufferedInputStream -import java.io.BufferedOutputStream import java.io.FileOutputStream -import java.util.zip.ZipEntry -import java.util.zip.ZipFile -import java.util.zip.ZipOutputStream +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream +import org.apache.commons.compress.archivers.zip.ZipFile import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor import org.objectweb.asm.ClassWriter @@ -93,13 +93,14 @@ class Ravenizer { val stats = RavenizerStats() stats.totalTime = log.nTime { - val allClasses = ClassNodes.loadClassStructures(options.inJar.get) { + val inJar = ZipFile(options.inJar.get) + val allClasses = ClassNodes.loadClassStructures(inJar, options.inJar.get) { stats.loadStructureTime = it } val processor = HostStubGenClassProcessor(options, allClasses) - process( - options.inJar.get, + inJar.process( + options.outJar.get, options.outJar.get, options.enableValidation.get, options.fatalValidation.get, @@ -111,7 +112,7 @@ class Ravenizer { log.i(stats.toString()) } - private fun process( + private fun ZipFile.process( inJar: String, outJar: String, enableValidation: Boolean, @@ -138,40 +139,34 @@ class Ravenizer { } stats.totalProcessTime = log.vTime("$executableName processing $inJar") { - ZipFile(inJar).use { inZip -> - val inEntries = inZip.entries() - - stats.totalEntries = inZip.size() - - ZipOutputStream(BufferedOutputStream(FileOutputStream(outJar))).use { outZip -> - while (inEntries.hasMoreElements()) { - val entry = inEntries.nextElement() - - if (entry.name.endsWith(".dex")) { - // Seems like it's an ART jar file. We can't process it. - // It's a fatal error. - throw GeneralUserErrorException( - "$inJar is not a desktop jar file. It contains a *.dex file." - ) - } + ZipArchiveOutputStream(FileOutputStream(outJar).buffered()).use { outZip -> + entries.asSequence().forEach { entry -> + stats.totalEntries++ + if (entry.name.endsWith(".dex")) { + // Seems like it's an ART jar file. We can't process it. + // It's a fatal error. + throw GeneralUserErrorException( + "$inJar is not a desktop jar file. It contains a *.dex file." + ) + } - if (stripMockito && entry.name.isMockitoFile()) { - // Skip this entry - continue - } + if (stripMockito && entry.name.isMockitoFile()) { + // Skip this entry + return@forEach + } - val className = zipEntryNameToClassName(entry.name) + val className = zipEntryNameToClassName(entry.name) - if (className != null) { - stats.totalClasses += 1 - } + if (className != null) { + stats.totalClasses += 1 + } - if (className != null && - shouldProcessClass(processor.allClasses, className)) { - processSingleClass(inZip, entry, outZip, processor, stats) - } else { - // Too slow, let's use merge_zips to bring back the original classes. - copyZipEntry(inZip, entry, outZip, stats) + if (className != null && + shouldProcessClass(processor.allClasses, className)) { + processSingleClass(this, entry, outZip, processor, stats) + } else { + stats.totalCopyTime += log.nTime { + copyZipEntry(this, entry, outZip) } } } @@ -179,53 +174,25 @@ class Ravenizer { } } - /** - * Copy a single ZIP entry to the output. - */ - private fun copyZipEntry( - inZip: ZipFile, - entry: ZipEntry, - out: ZipOutputStream, - stats: RavenizerStats, - ) { - stats.totalCopyTime += log.nTime { - inZip.getInputStream(entry).use { ins -> - // Copy unknown entries as is to the impl out. (but not to the stub out.) - val outEntry = ZipEntry(entry.name) - outEntry.method = 0 - outEntry.size = entry.size - outEntry.crc = entry.crc - out.putNextEntry(outEntry) - - ins.transferTo(out) - - out.closeEntry() - } - } - } - private fun processSingleClass( inZip: ZipFile, - entry: ZipEntry, - outZip: ZipOutputStream, + entry: ZipArchiveEntry, + outZip: ZipArchiveOutputStream, processor: HostStubGenClassProcessor, stats: RavenizerStats, ) { stats.processedClasses += 1 - val newEntry = ZipEntry(entry.name) - outZip.putNextEntry(newEntry) - - BufferedInputStream(inZip.getInputStream(entry)).use { bis -> - var classBytes = bis.readBytes() + inZip.getInputStream(entry).use { zis -> + var classBytes = zis.readAllBytes() stats.totalRavenizeTime += log.vTime("Ravenize ${entry.name}") { classBytes = ravenizeSingleClass(entry, classBytes, processor.allClasses) } stats.totalHostStubGenTime += log.vTime("HostStubGen ${entry.name}") { classBytes = processor.processClassBytecode(classBytes) } - outZip.write(classBytes) + // TODO: if the class does not change, use copyZipEntry + outZip.addBytesEntry(entry.name, classBytes) } - outZip.closeEntry() } /** @@ -237,7 +204,7 @@ class Ravenizer { } private fun ravenizeSingleClass( - entry: ZipEntry, + entry: ZipArchiveEntry, input: ByteArray, allClasses: ClassNodes, ): ByteArray { diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java index 658ea4c27e4c..a4cbf420b93b 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -168,7 +168,6 @@ public class AdbDebuggingManager { private AdbConnectionInfo mAdbConnectionInfo = new AdbConnectionInfo(); // Polls for a tls port property when adb wifi is enabled private AdbConnectionPortPoller mConnectionPortPoller; - private final PortListenerImpl mPortListener = new PortListenerImpl(); private final Ticker mTicker; public AdbDebuggingManager(Context context) { @@ -323,10 +322,6 @@ public class AdbDebuggingManager { } } - interface AdbConnectionPortListener { - void onPortReceived(int port); - } - /** * This class will poll for a period of time for adbd to write the port * it connected to. @@ -336,16 +331,11 @@ public class AdbDebuggingManager { * port through different means. A better fix would be to always start AdbDebuggingManager, but * it needs to adjust accordingly on whether ro.adb.secure is set. */ - static class AdbConnectionPortPoller extends Thread { + private class AdbConnectionPortPoller extends Thread { private final String mAdbPortProp = "service.adb.tls.port"; - private AdbConnectionPortListener mListener; private final int mDurationSecs = 10; private AtomicBoolean mCanceled = new AtomicBoolean(false); - AdbConnectionPortPoller(AdbConnectionPortListener listener) { - mListener = listener; - } - @Override public void run() { Slog.d(TAG, "Starting adb port property poller"); @@ -362,13 +352,22 @@ public class AdbDebuggingManager { // to start the server. Otherwise we should have a valid port. int port = SystemProperties.getInt(mAdbPortProp, Integer.MAX_VALUE); if (port == -1 || (port > 0 && port <= 65535)) { - mListener.onPortReceived(port); + onPortReceived(port); return; } SystemClock.sleep(1000); } Slog.w(TAG, "Failed to receive adb connection port"); - mListener.onPortReceived(-1); + onPortReceived(-1); + } + + private void onPortReceived(int port) { + Slog.d(TAG, "Received tls port=" + port); + Message msg = mHandler.obtainMessage(port > 0 + ? AdbDebuggingHandler.MSG_SERVER_CONNECTED + : AdbDebuggingHandler.MSG_SERVER_DISCONNECTED); + msg.obj = port; + mHandler.sendMessage(msg); } public void cancelAndWait() { @@ -382,17 +381,6 @@ public class AdbDebuggingManager { } } - class PortListenerImpl implements AdbConnectionPortListener { - public void onPortReceived(int port) { - Slog.d(TAG, "Received tls port=" + port); - Message msg = mHandler.obtainMessage(port > 0 - ? AdbDebuggingHandler.MSG_SERVER_CONNECTED - : AdbDebuggingHandler.MSG_SERVER_DISCONNECTED); - msg.obj = port; - mHandler.sendMessage(msg); - } - } - @VisibleForTesting static class AdbDebuggingThread extends Thread { private boolean mStopped; @@ -1088,8 +1076,7 @@ public class AdbDebuggingManager { mContext.registerReceiver(mBroadcastReceiver, intentFilter); SystemProperties.set(AdbService.WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); - mConnectionPortPoller = - new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener); + mConnectionPortPoller = new AdbDebuggingManager.AdbConnectionPortPoller(); mConnectionPortPoller.start(); startAdbDebuggingThread(); @@ -1138,8 +1125,7 @@ public class AdbDebuggingManager { mContext.registerReceiver(mBroadcastReceiver, intentFilter); SystemProperties.set(AdbService.WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); - mConnectionPortPoller = - new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener); + mConnectionPortPoller = new AdbDebuggingManager.AdbConnectionPortPoller(); mConnectionPortPoller.start(); startAdbDebuggingThread(); @@ -1257,7 +1243,7 @@ public class AdbDebuggingManager { if (mAdbWifiEnabled) { // In scenarios where adbd is restarted, the tls port may change. mConnectionPortPoller = - new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener); + new AdbDebuggingManager.AdbConnectionPortPoller(); mConnectionPortPoller.start(); } break; diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java index 40f7c873eae8..d12a0a2a1e00 100644 --- a/services/core/java/com/android/server/adb/AdbService.java +++ b/services/core/java/com/android/server/adb/AdbService.java @@ -21,10 +21,8 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManager; import android.database.ContentObserver; -import android.debug.AdbManager; import android.debug.AdbManagerInternal; import android.debug.AdbTransportType; import android.debug.FingerprintAndPairDevice; @@ -40,10 +38,8 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemProperties; -import android.os.UserHandle; import android.provider.Settings; import android.service.adb.AdbServiceDumpProto; -import android.sysprop.AdbProperties; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; @@ -63,7 +59,6 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collections; import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; /** * The Android Debug Bridge (ADB) service. This controls the availability of ADB and authorization @@ -85,12 +80,6 @@ public class AdbService extends IAdbManager.Stub { */ static final String CTL_STOP = "ctl.stop"; - // The tcp port adb is currently using - AtomicInteger mConnectionPort = new AtomicInteger(-1); - - private final AdbConnectionPortListener mPortListener = new AdbConnectionPortListener(); - private AdbDebuggingManager.AdbConnectionPortPoller mConnectionPortPoller; - private final RemoteCallbackList<IAdbCallback> mCallbacks = new RemoteCallbackList<>(); /** * Manages the service lifecycle for {@code AdbService} in {@code SystemServer}. @@ -404,39 +393,6 @@ public class AdbService extends IAdbManager.Stub { Slog.d(TAG, "Unregistering callback " + callback); mCallbacks.unregister(callback); } - /** - * This listener is only used when ro.adb.secure=0. Otherwise, AdbDebuggingManager will - * do this. - */ - class AdbConnectionPortListener implements AdbDebuggingManager.AdbConnectionPortListener { - public void onPortReceived(int port) { - if (port > 0 && port <= 65535) { - mConnectionPort.set(port); - } else { - mConnectionPort.set(-1); - // Turn off wifi debugging, since the server did not start. - try { - Settings.Global.putInt(mContentResolver, - Settings.Global.ADB_WIFI_ENABLED, 0); - } catch (SecurityException e) { - // If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't - // be changed. - Slog.d(TAG, "ADB_ENABLED is restricted."); - } - } - broadcastPortInfo(mConnectionPort.get()); - } - } - - private void broadcastPortInfo(int port) { - Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION); - intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, (port >= 0) - ? AdbManager.WIRELESS_STATUS_CONNECTED - : AdbManager.WIRELESS_STATUS_DISCONNECTED); - intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port); - AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL); - Slog.i(TAG, "sent port broadcast port=" + port); - } private void startAdbd() { SystemProperties.set(CTL_START, ADBD); @@ -470,20 +426,11 @@ public class AdbService extends IAdbManager.Stub { } else if (transportType == AdbTransportType.WIFI && enable != mIsAdbWifiEnabled) { mIsAdbWifiEnabled = enable; if (mIsAdbWifiEnabled) { - if (!AdbProperties.secure().orElse(false)) { - // Start adbd. If this is secure adb, then we defer enabling adb over WiFi. - SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); - mConnectionPortPoller = - new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener); - mConnectionPortPoller.start(); - } + // Start adb over WiFi. + SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); } else { // Stop adb over WiFi. SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "0"); - if (mConnectionPortPoller != null) { - mConnectionPortPoller.cancelAndWait(); - mConnectionPortPoller = null; - } } } else { // No change diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java index 7502664a9628..180ef855cfda 100644 --- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java +++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java @@ -39,6 +39,7 @@ import android.os.Trace; import android.os.UserHandle; import android.util.ArraySet; import android.util.IntArray; +import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.media.permission.INativePermissionController; @@ -62,6 +63,8 @@ import java.util.stream.Collectors; /** Responsible for synchronizing system server permission state to the native audioserver. */ public class AudioServerPermissionProvider { + static final String TAG = "AudioServerPermissionProvider"; + static final String[] MONITORED_PERMS = new String[PermissionEnum.ENUM_SIZE]; static final byte[] HDS_PERMS = new byte[] {PermissionEnum.CAPTURE_AUDIO_HOTWORD, @@ -219,10 +222,13 @@ public class AudioServerPermissionProvider { public void setIsolatedServiceUid(int uid, int owningUid) { synchronized (mLock) { if (mHdsUid == uid) return; - var packageNameSet = mPackageMap.get(owningUid); - if (packageNameSet == null) return; - var packageName = packageNameSet.iterator().next(); - onModifyPackageState(uid, packageName, /* isRemove= */ false); + var packageNameSet = mPackageMap.get(UserHandle.getAppId(owningUid)); + if (packageNameSet != null) { + var packageName = packageNameSet.iterator().next(); + onModifyPackageState(uid, packageName, /* isRemove= */ false); + } else { + Log.wtf(TAG, "setIsolatedService owning uid not found"); + } // permissions mHdsUid = uid; if (mDest == null) { @@ -249,11 +255,19 @@ public class AudioServerPermissionProvider { public void clearIsolatedServiceUid(int uid) { synchronized (mLock) { - if (mHdsUid != uid) return; - var packageNameSet = mPackageMap.get(uid); - if (packageNameSet == null) return; - var packageName = packageNameSet.iterator().next(); - onModifyPackageState(uid, packageName, /* isRemove= */ true); + var packageNameSet = mPackageMap.get(UserHandle.getAppId(uid)); + if (mHdsUid != uid) { + Log.wtf(TAG, + "Unexpected isolated service uid cleared: " + uid + packageNameSet + + ", expected " + mHdsUid); + return; + } + if (packageNameSet != null) { + var packageName = packageNameSet.iterator().next(); + onModifyPackageState(uid, packageName, /* isRemove= */ true); + } else { + Log.wtf(TAG, "clearIsolatedService uid not found"); + } // permissions if (mDest == null) { mIsUpdateDeferred = true; diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java index 9118c46e3b22..574e484edd16 100644 --- a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java +++ b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java @@ -167,7 +167,11 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction { private boolean handleReportPowerStatus(int powerStatus) { switch (powerStatus) { case HdmiControlManager.POWER_STATUS_ON: - selectDevice(); + if (tv().getActiveSource().physicalAddress == mTarget.getPhysicalAddress()) { + finishWithCallback(HdmiControlManager.RESULT_SUCCESS); + } else { + selectDevice(); + } return true; case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY: if (mPowerStatusCounter < 4) { diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index d9db178e0dc2..6e6d00d62819 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1230,7 +1230,7 @@ public class InputManagerService extends IInputManager.Stub "registerTabletModeChangedListener()")) { throw new SecurityException("Requires TABLET_MODE_LISTENER permission"); } - Objects.requireNonNull(listener, "event must not be null"); + Objects.requireNonNull(listener, "listener must not be null"); synchronized (mTabletModeLock) { final int callingPid = Binder.getCallingPid(); @@ -1342,7 +1342,7 @@ public class InputManagerService extends IInputManager.Stub @Override public void requestPointerCapture(IBinder inputChannelToken, boolean enabled) { - Objects.requireNonNull(inputChannelToken, "event must not be null"); + Objects.requireNonNull(inputChannelToken, "inputChannelToken must not be null"); mNative.requestPointerCapture(inputChannelToken, enabled); } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java index 6db62c8397f3..ccb9e3ea5cbe 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java @@ -301,7 +301,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub throw new IllegalArgumentException( "Unknown session ID in closeSession: id=" + sessionId); } - halCloseEndpointSession(sessionId, ContextHubServiceUtil.toHalReason(reason)); + mEndpointManager.halCloseEndpointSession( + sessionId, ContextHubServiceUtil.toHalReason(reason)); } @Override @@ -312,7 +313,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub // Iterate in reverse since cleanupSessionResources will remove the entry for (int i = mSessionMap.size() - 1; i >= 0; i--) { int id = mSessionMap.keyAt(i); - halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE); + mEndpointManager.halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE); cleanupSessionResources(id); } } @@ -444,7 +445,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub int id = mSessionMap.keyAt(i); HubEndpointInfo target = mSessionMap.get(id).getRemoteEndpointInfo(); if (!hasEndpointPermissions(target)) { - halCloseEndpointSessionNoThrow(id, Reason.PERMISSION_DENIED); + mEndpointManager.halCloseEndpointSessionNoThrow( + id, Reason.PERMISSION_DENIED); onCloseEndpointSession(id, Reason.PERMISSION_DENIED); // Resource cleanup is done in onCloseEndpointSession } @@ -503,17 +505,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */); } - /* package */ void onEndpointSessionOpenRequest( - int sessionId, HubEndpointInfo initiator, String serviceDescriptor) { - Optional<Byte> error = - onEndpointSessionOpenRequestInternal(sessionId, initiator, serviceDescriptor); - if (error.isPresent()) { - halCloseEndpointSessionNoThrow(sessionId, error.get()); - onCloseEndpointSession(sessionId, error.get()); - // Resource cleanup is done in onCloseEndpointSession - } - } - + /** Handle close endpoint callback to the client side */ /* package */ void onCloseEndpointSession(int sessionId, byte reason) { if (!cleanupSessionResources(sessionId)) { Log.w(TAG, "Unknown session ID in onCloseEndpointSession: id=" + sessionId); @@ -585,7 +577,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub } } - private Optional<Byte> onEndpointSessionOpenRequestInternal( + /* package */ Optional<Byte> onEndpointSessionOpenRequest( int sessionId, HubEndpointInfo initiator, String serviceDescriptor) { if (!hasEndpointPermissions(initiator)) { Log.e( @@ -594,15 +586,41 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub + initiator + " doesn't have permission for " + mEndpointInfo); - return Optional.of(Reason.PERMISSION_DENIED); + byte reason = Reason.PERMISSION_DENIED; + onCloseEndpointSession(sessionId, reason); + return Optional.of(reason); } + // Check & handle error cases for duplicated session id. + final boolean existingSession; + final boolean existingSessionActive; synchronized (mOpenSessionLock) { if (hasSessionId(sessionId)) { - Log.e(TAG, "Existing session in onEndpointSessionOpenRequest: id=" + sessionId); - return Optional.of(Reason.UNSPECIFIED); + existingSession = true; + existingSessionActive = mSessionMap.get(sessionId).isActive(); + Log.w( + TAG, + "onEndpointSessionOpenRequest: " + + "Existing session ID: " + + sessionId + + ", isActive: " + + existingSessionActive); + } else { + existingSession = false; + existingSessionActive = false; + mSessionMap.put(sessionId, new Session(initiator, true)); + } + } + + if (existingSession) { + if (existingSessionActive) { + // Existing session is already active, call onSessionOpenComplete. + openSessionRequestComplete(sessionId); + return Optional.empty(); } - mSessionMap.put(sessionId, new Session(initiator, true)); + // Reject the session open request for now. Consider invalidating previous pending + // session open request based on timeout. + return Optional.of(Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED); } boolean success = @@ -610,7 +628,11 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub (consumer) -> consumer.onSessionOpenRequest( sessionId, initiator, serviceDescriptor)); - return success ? Optional.empty() : Optional.of(Reason.UNSPECIFIED); + byte reason = Reason.UNSPECIFIED; + if (!success) { + onCloseEndpointSession(sessionId, reason); + } + return success ? Optional.empty() : Optional.of(reason); } private byte onMessageReceivedInternal(int sessionId, HubMessage message) { @@ -657,29 +679,6 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub } /** - * Calls the HAL closeEndpointSession API. - * - * @param sessionId The session ID to close - * @param halReason The HAL reason - */ - private void halCloseEndpointSession(int sessionId, byte halReason) throws RemoteException { - try { - mHubInterface.closeEndpointSession(sessionId, halReason); - } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) { - throw e; - } - } - - /** Same as halCloseEndpointSession but does not throw the exception */ - private void halCloseEndpointSessionNoThrow(int sessionId, byte halReason) { - try { - halCloseEndpointSession(sessionId, halReason); - } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) { - Log.e(TAG, "Exception while calling HAL closeEndpointSession", e); - } - } - - /** * Cleans up resources related to a session with the provided ID. * * @param sessionId The session ID to clean up resources for diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java index 8ab581e1fb7a..e1561599517d 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java @@ -29,6 +29,7 @@ import android.hardware.contexthub.IContextHubEndpoint; import android.hardware.contexthub.IContextHubEndpointCallback; import android.hardware.contexthub.IEndpointCommunication; import android.hardware.contexthub.MessageDeliveryStatus; +import android.hardware.contexthub.Reason; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; @@ -42,6 +43,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; @@ -316,6 +318,11 @@ import java.util.function.Consumer; } } + /** Returns if a sessionId can be allocated for the service hub. */ + private boolean isSessionIdAllocatedForService(int sessionId) { + return sessionId > mMaxSessionId || sessionId < mMinSessionId; + } + /** * Unregisters an endpoint given its ID. * @@ -337,8 +344,7 @@ import java.util.function.Consumer; } } - @Override - public void onEndpointSessionOpenRequest( + private Optional<Byte> onEndpointSessionOpenRequestInternal( int sessionId, HubEndpointInfo.HubEndpointIdentifier destination, HubEndpointInfo.HubEndpointIdentifier initiator, @@ -348,7 +354,7 @@ import java.util.function.Consumer; TAG, "onEndpointSessionOpenRequest: invalid destination hub ID: " + destination.getHub()); - return; + return Optional.of(Reason.ENDPOINT_INVALID); } ContextHubEndpointBroker broker = mEndpointMap.get(destination.getEndpoint()); if (broker == null) { @@ -356,7 +362,7 @@ import java.util.function.Consumer; TAG, "onEndpointSessionOpenRequest: unknown destination endpoint ID: " + destination.getEndpoint()); - return; + return Optional.of(Reason.ENDPOINT_INVALID); } HubEndpointInfo initiatorInfo = mHubInfoRegistry.getEndpointInfo(initiator); if (initiatorInfo == null) { @@ -364,9 +370,29 @@ import java.util.function.Consumer; TAG, "onEndpointSessionOpenRequest: unknown initiator endpoint ID: " + initiator.getEndpoint()); - return; + return Optional.of(Reason.ENDPOINT_INVALID); + } + if (!isSessionIdAllocatedForService(sessionId)) { + Log.e( + TAG, + "onEndpointSessionOpenRequest: invalid session ID, rejected:" + + " sessionId=" + + sessionId); + return Optional.of(Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED); } - broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor); + return broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor); + } + + @Override + public void onEndpointSessionOpenRequest( + int sessionId, + HubEndpointInfo.HubEndpointIdentifier destination, + HubEndpointInfo.HubEndpointIdentifier initiator, + String serviceDescriptor) { + Optional<Byte> errorOptional = + onEndpointSessionOpenRequestInternal( + sessionId, destination, initiator, serviceDescriptor); + errorOptional.ifPresent((error) -> halCloseEndpointSessionNoThrow(sessionId, error)); } @Override @@ -418,6 +444,30 @@ import java.util.function.Consumer; } } + /** + * Calls the HAL closeEndpointSession API. + * + * @param sessionId The session ID to close + * @param halReason The HAL reason + */ + /* package */ void halCloseEndpointSession(int sessionId, byte halReason) + throws RemoteException { + try { + mHubInterface.closeEndpointSession(sessionId, halReason); + } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) { + throw e; + } + } + + /** Same as halCloseEndpointSession but does not throw the exception */ + /* package */ void halCloseEndpointSessionNoThrow(int sessionId, byte halReason) { + try { + halCloseEndpointSession(sessionId, halReason); + } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) { + Log.e(TAG, "Exception while calling HAL closeEndpointSession", e); + } + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java index 7b4c56334868..7fd400ec8ac9 100644 --- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java +++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java @@ -518,7 +518,6 @@ public class GnssNative { if (!Flags.gnssAssistanceInterfaceJni()) { return; } - Preconditions.checkState(!mRegistered); Preconditions.checkState(mGnssAssistanceCallbacks == null); mGnssAssistanceCallbacks = Objects.requireNonNull(callbacks); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 8948bd196789..78554bdcf6aa 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -6173,10 +6173,15 @@ public class NotificationManagerService extends SystemService { } @Override - public Map<String, AutomaticZenRule> getAutomaticZenRules() { + public ParceledListSlice getAutomaticZenRules() { int callingUid = Binder.getCallingUid(); enforcePolicyAccess(callingUid, "getAutomaticZenRules"); - return mZenModeHelper.getAutomaticZenRules(getCallingZenUser(), callingUid); + List<AutomaticZenRule.AzrWithId> ruleList = new ArrayList<>(); + for (Map.Entry<String, AutomaticZenRule> rule : mZenModeHelper.getAutomaticZenRules( + getCallingZenUser(), callingUid).entrySet()) { + ruleList.add(new AutomaticZenRule.AzrWithId(rule.getKey(), rule.getValue())); + } + return new ParceledListSlice<>(ruleList); } @Override diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java index 6872ca9e46ee..f5daa8036726 100644 --- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java +++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java @@ -59,6 +59,7 @@ import com.android.server.security.advancedprotection.features.DisallowCellular2 import com.android.server.security.advancedprotection.features.DisallowInstallUnknownSourcesAdvancedProtectionHook; import com.android.server.security.advancedprotection.features.MemoryTaggingExtensionHook; import com.android.server.security.advancedprotection.features.UsbDataAdvancedProtectionHook; +import com.android.server.security.advancedprotection.features.DisallowWepAdvancedProtectionProvider; import java.io.File; import java.io.FileDescriptor; @@ -131,7 +132,9 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub } catch (Exception e) { Slog.e(TAG, "Failed to initialize UsbDataAdvancedProtection", e); } - } + } + + mProviders.add(new DisallowWepAdvancedProtectionProvider()); } // Only for tests @@ -303,7 +306,7 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub getAdvancedProtectionFeatures_enforcePermission(); List<AdvancedProtectionFeature> features = new ArrayList<>(); for (int i = 0; i < mProviders.size(); i++) { - features.addAll(mProviders.get(i).getFeatures()); + features.addAll(mProviders.get(i).getFeatures(mContext)); } for (int i = 0; i < mHooks.size(); i++) { @@ -341,7 +344,7 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub writer.println(" Providers: "); mProviders.stream().forEach(provider -> { writer.println(" " + provider.getClass().getSimpleName()); - provider.getFeatures().stream().forEach(feature -> { + provider.getFeatures(mContext).stream().forEach(feature -> { writer.println(" " + feature.getClass().getSimpleName()); }); }); diff --git a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java index ed451f1e2257..6498cfc97aac 100644 --- a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java +++ b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java @@ -16,6 +16,8 @@ package com.android.server.security.advancedprotection.features; +import android.annotation.NonNull; +import android.content.Context; import android.security.advancedprotection.AdvancedProtectionFeature; import java.util.List; @@ -23,5 +25,5 @@ import java.util.List; /** @hide */ public abstract class AdvancedProtectionProvider { /** The list of features provided */ - public abstract List<AdvancedProtectionFeature> getFeatures(); + public abstract List<AdvancedProtectionFeature> getFeatures(@NonNull Context context); } diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowWepAdvancedProtectionProvider.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowWepAdvancedProtectionProvider.java new file mode 100644 index 000000000000..1505f68b699e --- /dev/null +++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowWepAdvancedProtectionProvider.java @@ -0,0 +1,31 @@ +/* + * 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.server.security.advancedprotection.features; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.wifi.WifiManager; +import android.security.advancedprotection.AdvancedProtectionFeature; + +import java.util.List; + +public class DisallowWepAdvancedProtectionProvider extends AdvancedProtectionProvider { + public List<AdvancedProtectionFeature> getFeatures(@NonNull Context context) { + WifiManager wifiManager = context.getSystemService(WifiManager.class); + return wifiManager.getAvailableAdvancedProtectionFeatures(); + } +} diff --git a/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java index 959609309da1..cd6a01478eef 100644 --- a/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java @@ -156,6 +156,8 @@ class AppCompatSafeRegionPolicy { void dump(@NonNull PrintWriter pw, @NonNull String prefix) { if (mNeedsSafeRegionBounds) { pw.println(prefix + " mNeedsSafeRegionBounds=true"); + pw.println( + prefix + " latestSafeRegionBoundsOnActivity=" + getLatestSafeRegionBounds()); } if (isLetterboxedForSafeRegionOnlyAllowed()) { pw.println(prefix + " isLetterboxForSafeRegionOnlyAllowed=true"); diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index 2798e843d6dd..ab87459da01a 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -218,6 +218,11 @@ class Dimmer { */ protected void adjustAppearance(@NonNull WindowState dimmingContainer, float alpha, int blurRadius) { + if (!mHost.isVisibleRequested()) { + // If the host is already going away, there is no point in keeping dimming + return; + } + if (mDimState != null || (alpha != 0 || blurRadius != 0)) { final DimState d = obtainDimState(dimmingContainer); d.prepareLookChange(alpha, blurRadius); diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index bdd13722aba4..d356128205df 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -337,6 +337,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio public static final int ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE = 1 << 25; public static final int ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER = 0x0000ffff; + private static final int ACTIVITY_STATE_VISIBLE = + com.android.window.flags.Flags.useVisibleRequestedForProcessTracker() + ? ACTIVITY_STATE_FLAG_IS_VISIBLE + : ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE; + /** * The state for oom-adjustment calculation. The higher 16 bits are the activity states, and the * lower 16 bits are the task layer rank (see {@link Task#mLayerRank}). This field is written by @@ -1260,8 +1265,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio int nonOccludedRatio = 0; long perceptibleTaskStoppedTimeMillis = Long.MIN_VALUE; final boolean wasResumed = hasResumedActivity(); - final boolean wasAnyVisible = (mActivityStateFlags - & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0; + final boolean wasAnyVisible = (mActivityStateFlags & ACTIVITY_STATE_VISIBLE) != 0; for (int i = mActivities.size() - 1; i >= 0; i--) { final ActivityRecord r = mActivities.get(i); if (r.isVisible()) { @@ -1275,8 +1279,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio if (task.mLayerRank != Task.LAYER_RANK_INVISIBLE) { stateFlags |= ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK; } + final ActivityRecord.State state = r.getState(); if (r.isVisibleRequested()) { - if (r.isState(RESUMED)) { + if (state == RESUMED) { stateFlags |= ACTIVITY_STATE_FLAG_HAS_RESUMED; final int windowingMode = r.getWindowingMode(); if (windowingMode == WINDOWING_MODE_MULTI_WINDOW @@ -1301,13 +1306,21 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // this process, we'd find out the one with the minimal layer, thus it'll // get a higher adj score. } else if (!visible && bestInvisibleState != PAUSING) { - if (r.isState(PAUSING, PAUSED)) { + if (state == PAUSING) { bestInvisibleState = PAUSING; - } else if (r.isState(STOPPING)) { + // Treat PAUSING as visible in case the next activity in the same process has + // not yet been set as visible-requested. + if (com.android.window.flags.Flags.useVisibleRequestedForProcessTracker() + && r.isVisible()) { + stateFlags |= ACTIVITY_STATE_FLAG_IS_VISIBLE; + } + } else if (state == PAUSED) { + bestInvisibleState = PAUSED; + } else if (state == STOPPING) { bestInvisibleState = STOPPING; // Not "finishing" if any of activity isn't finishing. allStoppingFinishing &= r.finishing; - } else if (bestInvisibleState == DESTROYED && r.isState(STOPPED)) { + } else if (bestInvisibleState == DESTROYED && state == STOPPED) { if (task.mIsPerceptible) { perceptibleTaskStoppedTimeMillis = Long.max(r.mStoppedTime, perceptibleTaskStoppedTimeMillis); @@ -1340,7 +1353,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER; if (visible) { stateFlags |= ACTIVITY_STATE_FLAG_IS_VISIBLE; - } else if (bestInvisibleState == PAUSING) { + } else if (bestInvisibleState == PAUSING || bestInvisibleState == PAUSED) { stateFlags |= ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED; } else if (bestInvisibleState == STOPPING) { stateFlags |= ACTIVITY_STATE_FLAG_IS_STOPPING; @@ -1351,8 +1364,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio mActivityStateFlags = stateFlags; mPerceptibleTaskStoppedTimeMillis = perceptibleTaskStoppedTimeMillis; - final boolean anyVisible = (stateFlags - & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0; + final boolean anyVisible = (stateFlags & ACTIVITY_STATE_VISIBLE) != 0; if (!wasAnyVisible && anyVisible) { mAtm.mVisibleActivityProcessTracker.onAnyActivityVisible(this); mAtm.mWindowManager.onProcessActivityVisibilityChanged(mUid, true /*visible*/); diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java index f6b107b60d62..d60807c7b001 100644 --- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java @@ -62,6 +62,8 @@ public class PrepareGetRequestSession extends GetRequestSession { Collectors.toSet())).size(); // Dedupe type strings mRequestSessionMetric.collectGetFlowInitialMetricInfo(request); mPrepareGetCredentialCallback = prepareGetCredentialCallback; + + Slog.i(TAG, "PrepareGetRequestSession constructed."); } @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 51ed6bb2aa40..f055febca3d5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -276,6 +276,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS; import static com.android.server.devicepolicy.DevicePolicyEngine.DEFAULT_POLICY_SIZE_LIMIT; +import static com.android.server.devicepolicy.DevicePolicyEngine.SYSTEM_SUPERVISION_ROLE; import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE; import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__COPE; import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__DEVICE_OWNER; @@ -16296,6 +16297,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return null; } + /** + * When multiple admins enforce a policy, this method returns an admin according to this order: + * 1. Supervision + * 2. DPC + * + * Otherwise, it returns any other admin. + */ private android.app.admin.EnforcingAdmin getEnforcingAdminInternal(int userId, String identifier) { Objects.requireNonNull(identifier); @@ -16304,16 +16312,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (admins.isEmpty()) { return null; } - - final EnforcingAdmin admin; if (admins.size() == 1) { - admin = admins.iterator().next(); - } else { - Optional<EnforcingAdmin> dpc = admins.stream() - .filter(a -> a.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)).findFirst(); - admin = dpc.orElseGet(() -> admins.stream().findFirst().get()); + return admins.iterator().next().getParcelableAdmin(); + } + Optional<EnforcingAdmin> supervision = admins.stream() + .filter(a -> a.hasAuthority( + EnforcingAdmin.getRoleAuthorityOf(SYSTEM_SUPERVISION_ROLE))) + .findFirst(); + if (supervision.isPresent()) { + return supervision.get().getParcelableAdmin(); + } + Optional<EnforcingAdmin> dpc = admins.stream() + .filter(a -> a.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)).findFirst(); + if (dpc.isPresent()) { + return dpc.get().getParcelableAdmin(); } - return admin == null ? null : admin.getParcelableAdmin(); + return admins.iterator().next().getParcelableAdmin(); } private <V> Set<EnforcingAdmin> getEnforcingAdminsForIdentifier(int userId, String identifier) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 543e32fae55f..9ff6eb66064f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -34,6 +34,7 @@ import android.app.admin.PackagePolicyKey; import android.app.admin.PolicyKey; import android.app.admin.PolicyValue; import android.app.admin.UserRestrictionPolicyKey; +import android.app.admin.flags.Flags; import android.content.ComponentName; import android.content.Context; import android.content.IntentFilter; @@ -282,7 +283,9 @@ final class PolicyDefinition<V> { static PolicyDefinition<Set<String>> PERMITTED_INPUT_METHODS = new PolicyDefinition<>( new NoArgsPolicyKey(DevicePolicyIdentifiers.PERMITTED_INPUT_METHODS_POLICY), - new MostRecent<>(), + (Flags.usePolicyIntersectionForPermittedInputMethods() + ? new StringSetIntersection() + : new MostRecent<>()), POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE, PolicyEnforcerCallbacks::noOp, new PackageSetPolicySerializer()); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java new file mode 100644 index 000000000000..bc075b02b141 --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java @@ -0,0 +1,59 @@ +/* + * 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.server.devicepolicy; + +import android.annotation.NonNull; +import android.app.admin.PolicyValue; +import android.app.admin.PackageSetPolicyValue; + +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Objects; +import java.util.Set; + +final class StringSetIntersection extends ResolutionMechanism<Set<String>> { + + @Override + PolicyValue<Set<String>> resolve( + @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<Set<String>>> adminPolicies) { + Objects.requireNonNull(adminPolicies); + Set<String> intersectionOfPolicies = null; + for (PolicyValue<Set<String>> policy : adminPolicies.values()) { + if (intersectionOfPolicies == null) { + intersectionOfPolicies = new HashSet<>(policy.getValue()); + } else { + intersectionOfPolicies.retainAll(policy.getValue()); + } + } + if (intersectionOfPolicies == null) { + return null; + } + // Note that the resulting set below may be empty, but that's fine: + // particular policy should decide what is the meaning of an empty set. + return new PackageSetPolicyValue(intersectionOfPolicies); + } + + @Override + android.app.admin.StringSetIntersection getParcelableResolutionMechanism() { + return new android.app.admin.StringSetIntersection(); + } + + @Override + public String toString() { + return "StringSetIntersection {}"; + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java index a4c71bd6094e..2227eebdb128 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java @@ -251,6 +251,35 @@ public class DeviceSelectActionFromTvTest { } @Test + public void testDeviceSelect_DeviceAssertsActiveSource_singleSetStreamPathMessage() { + // TV was watching playback2 device connected at port 2, and wants to select + // playback1. + TestActionTimer actionTimer = new TestActionTimer(); + TestCallback callback = new TestCallback(); + DeviceSelectActionFromTv action = createDeviceSelectAction(actionTimer, callback, + /*isCec20=*/false); + mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, + "testDeviceSelect"); + action.start(); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + mNativeWrapper.clearResultMessages(); + mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYBACK_1, + "testDeviceSelect"); + mTestLooper.dispatchAll(); + + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); + action.processCommand(REPORT_POWER_STATUS_ON); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test public void testDeviceSelect_DeviceInStandbyStatus_Cec14b() { mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, "testDeviceSelect"); diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java index 4d2dcf65bfeb..43b1ec393bf6 100644 --- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java +++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -67,12 +68,15 @@ public class ContextHubEndpointTest { private static final int SESSION_ID_RANGE = ContextHubEndpointManager.SERVICE_SESSION_RANGE; private static final int MIN_SESSION_ID = 0; private static final int MAX_SESSION_ID = MIN_SESSION_ID + SESSION_ID_RANGE - 1; + private static final int SESSION_ID_FOR_OPEN_REQUEST = MAX_SESSION_ID + 1; + private static final int INVALID_SESSION_ID_FOR_OPEN_REQUEST = MIN_SESSION_ID + 1; private static final String ENDPOINT_NAME = "Example test endpoint"; private static final int ENDPOINT_ID = 1; private static final String ENDPOINT_PACKAGE_NAME = "com.android.server.location.contexthub"; private static final String TARGET_ENDPOINT_NAME = "Example target endpoint"; + private static final String ENDPOINT_SERVICE_DESCRIPTOR = "serviceDescriptor"; private static final int TARGET_ENDPOINT_ID = 1; private static final int SAMPLE_MESSAGE_TYPE = 1234; @@ -225,6 +229,105 @@ public class ContextHubEndpointTest { } @Test + public void testEndpointSessionOpenRequest() throws RemoteException { + assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE); + IContextHubEndpoint endpoint = registerExampleEndpoint(); + + HubEndpointInfo targetInfo = + new HubEndpointInfo( + TARGET_ENDPOINT_NAME, + TARGET_ENDPOINT_ID, + ENDPOINT_PACKAGE_NAME, + Collections.emptyList()); + mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo}); + mEndpointManager.onEndpointSessionOpenRequest( + SESSION_ID_FOR_OPEN_REQUEST, + endpoint.getAssignedHubEndpointInfo().getIdentifier(), + targetInfo.getIdentifier(), + ENDPOINT_SERVICE_DESCRIPTOR); + + verify(mMockCallback) + .onSessionOpenRequest( + SESSION_ID_FOR_OPEN_REQUEST, targetInfo, ENDPOINT_SERVICE_DESCRIPTOR); + + // Accept + endpoint.openSessionRequestComplete(SESSION_ID_FOR_OPEN_REQUEST); + verify(mMockEndpointCommunications) + .endpointSessionOpenComplete(SESSION_ID_FOR_OPEN_REQUEST); + + unregisterExampleEndpoint(endpoint); + } + + @Test + public void testEndpointSessionOpenRequestWithInvalidSessionId() throws RemoteException { + assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE); + IContextHubEndpoint endpoint = registerExampleEndpoint(); + + HubEndpointInfo targetInfo = + new HubEndpointInfo( + TARGET_ENDPOINT_NAME, + TARGET_ENDPOINT_ID, + ENDPOINT_PACKAGE_NAME, + Collections.emptyList()); + mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo}); + mEndpointManager.onEndpointSessionOpenRequest( + INVALID_SESSION_ID_FOR_OPEN_REQUEST, + endpoint.getAssignedHubEndpointInfo().getIdentifier(), + targetInfo.getIdentifier(), + ENDPOINT_SERVICE_DESCRIPTOR); + verify(mMockEndpointCommunications) + .closeEndpointSession( + INVALID_SESSION_ID_FOR_OPEN_REQUEST, + Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED); + verify(mMockCallback, never()) + .onSessionOpenRequest( + INVALID_SESSION_ID_FOR_OPEN_REQUEST, + targetInfo, + ENDPOINT_SERVICE_DESCRIPTOR); + + unregisterExampleEndpoint(endpoint); + } + + @Test + public void testEndpointSessionOpenRequest_duplicatedSessionId_noopWhenSessionIsActive() + throws RemoteException { + assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE); + IContextHubEndpoint endpoint = registerExampleEndpoint(); + + HubEndpointInfo targetInfo = + new HubEndpointInfo( + TARGET_ENDPOINT_NAME, + TARGET_ENDPOINT_ID, + ENDPOINT_PACKAGE_NAME, + Collections.emptyList()); + mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo}); + mEndpointManager.onEndpointSessionOpenRequest( + SESSION_ID_FOR_OPEN_REQUEST, + endpoint.getAssignedHubEndpointInfo().getIdentifier(), + targetInfo.getIdentifier(), + ENDPOINT_SERVICE_DESCRIPTOR); + endpoint.openSessionRequestComplete(SESSION_ID_FOR_OPEN_REQUEST); + // Now session with id SESSION_ID_FOR_OPEN_REQUEST is active + + // Duplicated session open request + mEndpointManager.onEndpointSessionOpenRequest( + SESSION_ID_FOR_OPEN_REQUEST, + endpoint.getAssignedHubEndpointInfo().getIdentifier(), + targetInfo.getIdentifier(), + ENDPOINT_SERVICE_DESCRIPTOR); + + // Client API is only invoked once + verify(mMockCallback, times(1)) + .onSessionOpenRequest( + SESSION_ID_FOR_OPEN_REQUEST, targetInfo, ENDPOINT_SERVICE_DESCRIPTOR); + // HAL still receives two open complete notifications + verify(mMockEndpointCommunications, times(2)) + .endpointSessionOpenComplete(SESSION_ID_FOR_OPEN_REQUEST); + + unregisterExampleEndpoint(endpoint); + } + + @Test public void testMessageTransaction() throws RemoteException { IContextHubEndpoint endpoint = registerExampleEndpoint(); testMessageTransactionInternal(endpoint, /* deliverMessageStatus= */ true); diff --git a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java index c7a06b8eec7b..339bac4f768b 100644 --- a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java @@ -259,7 +259,7 @@ public class AdvancedProtectionServiceTest { AdvancedProtectionProvider provider = new AdvancedProtectionProvider() { @Override - public List<AdvancedProtectionFeature> getFeatures() { + public List<AdvancedProtectionFeature> getFeatures(Context context) { return List.of(feature2); } }; @@ -291,7 +291,7 @@ public class AdvancedProtectionServiceTest { AdvancedProtectionProvider provider = new AdvancedProtectionProvider() { @Override - public List<AdvancedProtectionFeature> getFeatures() { + public List<AdvancedProtectionFeature> getFeatures(Context context) { return List.of(feature2); } }; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index bc8b7becc919..902171d614d9 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -16844,22 +16844,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void updateAutomaticZenRule_implicitRuleWithoutCPS_disallowedFromApp() throws Exception { setUpRealZenTest(); mService.setCallerIsNormalPackage(); - assertThat(mBinderService.getAutomaticZenRules()).isEmpty(); + assertThat(mBinderService.getAutomaticZenRules().getList()).isEmpty(); // Create an implicit zen rule by calling setNotificationPolicy from an app. mBinderService.setNotificationPolicy(mPkg, new NotificationManager.Policy(0, 0, 0), false); - assertThat(mBinderService.getAutomaticZenRules()).hasSize(1); - Map.Entry<String, AutomaticZenRule> rule = getOnlyElement( - mBinderService.getAutomaticZenRules().entrySet()); - assertThat(rule.getValue().getOwner()).isNull(); - assertThat(rule.getValue().getConfigurationActivity()).isNull(); + assertThat(mBinderService.getAutomaticZenRules().getList()).hasSize(1); + AutomaticZenRule.AzrWithId rule = getOnlyElement( + (List<AutomaticZenRule.AzrWithId>) mBinderService.getAutomaticZenRules().getList()); + assertThat(rule.mRule.getOwner()).isNull(); + assertThat(rule.mRule.getConfigurationActivity()).isNull(); // Now try to update said rule (e.g. disable it). Should fail. // We also validate the exception message because NPE could be thrown by all sorts of test // issues (e.g. misconfigured mocks). - rule.getValue().setEnabled(false); + rule.mRule.setEnabled(false); NullPointerException e = assertThrows(NullPointerException.class, - () -> mBinderService.updateAutomaticZenRule(rule.getKey(), rule.getValue(), false)); + () -> mBinderService.updateAutomaticZenRule(rule.mId, rule.mRule, false)); assertThat(e.getMessage()).isEqualTo( "Rule must have a ConditionProviderService and/or configuration activity"); } @@ -16869,24 +16869,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void updateAutomaticZenRule_implicitRuleWithoutCPS_allowedFromSystem() throws Exception { setUpRealZenTest(); mService.setCallerIsNormalPackage(); - assertThat(mBinderService.getAutomaticZenRules()).isEmpty(); + assertThat(mBinderService.getAutomaticZenRules().getList()).isEmpty(); // Create an implicit zen rule by calling setNotificationPolicy from an app. mBinderService.setNotificationPolicy(mPkg, new NotificationManager.Policy(0, 0, 0), false); - assertThat(mBinderService.getAutomaticZenRules()).hasSize(1); - Map.Entry<String, AutomaticZenRule> rule = getOnlyElement( - mBinderService.getAutomaticZenRules().entrySet()); - assertThat(rule.getValue().getOwner()).isNull(); - assertThat(rule.getValue().getConfigurationActivity()).isNull(); + assertThat(mBinderService.getAutomaticZenRules().getList()).hasSize(1); + AutomaticZenRule.AzrWithId rule = getOnlyElement( + (List<AutomaticZenRule.AzrWithId>) mBinderService.getAutomaticZenRules().getList()); + assertThat(rule.mRule.getOwner()).isNull(); + assertThat(rule.mRule.getConfigurationActivity()).isNull(); // Now update said rule from Settings (e.g. disable it). Should work! mService.isSystemUid = true; - rule.getValue().setEnabled(false); - mBinderService.updateAutomaticZenRule(rule.getKey(), rule.getValue(), false); + rule.mRule.setEnabled(false); + mBinderService.updateAutomaticZenRule(rule.mId, rule.mRule, false); - Map.Entry<String, AutomaticZenRule> updatedRule = getOnlyElement( - mBinderService.getAutomaticZenRules().entrySet()); - assertThat(updatedRule.getValue().isEnabled()).isFalse(); + AutomaticZenRule.AzrWithId updatedRule = getOnlyElement( + (List<AutomaticZenRule.AzrWithId>) mBinderService.getAutomaticZenRules().getList()); + assertThat(updatedRule.mRule.isEnabled()).isFalse(); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java index 9dc70266bf3d..5347f9a36652 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -382,12 +382,17 @@ public class WindowProcessControllerTests extends WindowTestsBase { assertFalse(tracker.hasResumedActivity(mWpc.mUid)); assertTrue(mWpc.hasForegroundActivities()); - activity.setVisibility(false); activity.setVisibleRequested(false); - activity.setState(STOPPED, "test"); - + if (com.android.window.flags.Flags.useVisibleRequestedForProcessTracker()) { + assertTrue("PAUSING is visible", mWpc.hasVisibleActivities()); + activity.setState(PAUSED, "test"); + } else { + activity.setVisible(false); + } verify(tracker).onAllActivitiesInvisible(mWpc); assertFalse(mWpc.hasVisibleActivities()); + + activity.setState(STOPPED, "test"); assertFalse(mWpc.hasForegroundActivities()); } diff --git a/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java b/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java index d9bb7db17685..5419d9444abd 100644 --- a/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java +++ b/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java @@ -110,7 +110,7 @@ public class TextToSpeechTests extends InstrumentationTestCase { blockingCallSpeak("foo bar", delegate); ArgumentCaptor<SynthesisRequest> req = ArgumentCaptor.forClass(SynthesisRequest.class); Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req.capture(), - Mockito.<SynthesisCallback>anyObject()); + Mockito.<SynthesisCallback>any()); assertEquals("eng", req.getValue().getLanguage()); assertEquals("USA", req.getValue().getCountry()); @@ -133,7 +133,7 @@ public class TextToSpeechTests extends InstrumentationTestCase { blockingCallSpeak("le fou barre", delegate); ArgumentCaptor<SynthesisRequest> req2 = ArgumentCaptor.forClass(SynthesisRequest.class); Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req2.capture(), - Mockito.<SynthesisCallback>anyObject()); + Mockito.<SynthesisCallback>any()); // The params are basically unchanged. assertEquals("eng", req2.getValue().getLanguage()); @@ -177,7 +177,7 @@ public class TextToSpeechTests extends InstrumentationTestCase { blockingCallSpeak("foo bar", delegate); ArgumentCaptor<SynthesisRequest> req = ArgumentCaptor.forClass(SynthesisRequest.class); Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req.capture(), - Mockito.<SynthesisCallback>anyObject()); + Mockito.<SynthesisCallback>any()); assertEquals(defaultLocale.getISO3Language(), req.getValue().getLanguage()); assertEquals(defaultLocale.getISO3Country(), req.getValue().getCountry()); @@ -189,8 +189,8 @@ public class TextToSpeechTests extends InstrumentationTestCase { private void blockingCallSpeak(String speech, IDelegate mock) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); - doCountDown(latch).when(mock).onSynthesizeText(Mockito.<SynthesisRequest>anyObject(), - Mockito.<SynthesisCallback>anyObject()); + doCountDown(latch).when(mock).onSynthesizeText(Mockito.<SynthesisRequest>any(), + Mockito.<SynthesisCallback>any()); mTts.speak(speech, TextToSpeech.QUEUE_ADD, null); awaitCountDown(latch, 5, TimeUnit.SECONDS); |