diff options
82 files changed, 1234 insertions, 351 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index fbc8eefdd945..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>); 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/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/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/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/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/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/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/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/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/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..7cfd3e70d36d 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -2136,3 +2136,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/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/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/NotificationEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt index 8efc031490d1..12ade62c3570 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 @@ -365,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() 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/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/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/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3fdb98b4e00b..924eaa47bde6 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> 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/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/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java index fcdcc3f698de..3bb1ff161b6f 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, 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 2967c657eef1..e743d87a784c 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 @@ -126,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 dbb5c1046215..f39bd0324995 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 @@ -144,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 bda28a9402bb..12cfa91d9890 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 @@ -149,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/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 0661b1c3638d..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; @@ -2520,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()); + } } } @@ -4422,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/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/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/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/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/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/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/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/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/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/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 |