diff options
43 files changed, 1258 insertions, 344 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index dd919cacc534..a0f38d9a7bd9 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -680,6 +680,11 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +cc_aconfig_library { + name: "com.android.media.flags.editing-aconfig-cc", + aconfig_declarations: "com.android.media.flags.editing-aconfig", +} + // MediaProjection aconfig_declarations { name: "com.android.media.flags.projection-aconfig", diff --git a/core/api/current.txt b/core/api/current.txt index d0e20031c74d..f994e7055682 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -6495,6 +6495,7 @@ package android.app { field public static final int FLAG_NO_CLEAR = 32; // 0x20 field public static final int FLAG_ONGOING_EVENT = 2; // 0x2 field public static final int FLAG_ONLY_ALERT_ONCE = 8; // 0x8 + field @FlaggedApi("android.app.api_rich_ongoing") public static final int FLAG_PROMOTED_ONGOING = 262144; // 0x40000 field @Deprecated public static final int FLAG_SHOW_LIGHTS = 1; // 0x1 field public static final int FOREGROUND_SERVICE_DEFAULT = 0; // 0x0 field public static final int FOREGROUND_SERVICE_DEFERRED = 2; // 0x2 diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index e2bee64cbb7b..b9fe356690e0 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -258,4 +258,6 @@ interface INotificationManager @EnforcePermission(allOf={"INTERACT_ACROSS_USERS", "ACCESS_NOTIFICATIONS"}) void unregisterCallNotificationEventListener(String packageName, in UserHandle userHandle, in ICallNotificationEventCallback listener); + void setCanBePromoted(String pkg, int uid, boolean promote); + boolean canBePromoted(String pkg, int uid); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 81d2c890ee31..e32702e2e930 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -772,6 +772,17 @@ public class Notification implements Parcelable @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG) public static final int FLAG_SILENT = 1 << 17; //0x00020000 + /** + * Bit to be bitwise-ored into the {@link #flags} field that should be + * set by the system if this notification is a promoted ongoing notification, either via a + * user setting or allowlist. + * + * Applications cannot set this flag directly, but the posting app and + * {@link android.service.notification.NotificationListenerService} can read it. + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public static final int FLAG_PROMOTED_ONGOING = 0x00040000; + private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList( BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, @@ -3110,6 +3121,53 @@ public class Notification implements Parcelable } /** + * @hide + */ + @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING) + public boolean containsCustomViews() { + return contentView != null + || bigContentView != null + || headsUpContentView != null + || (publicVersion != null + && (publicVersion.contentView != null + || publicVersion.bigContentView != null + || publicVersion.headsUpContentView != null)); + } + + /** + * @hide + */ + @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING) + public boolean hasTitle() { + return extras != null + && (!TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE)) + || !TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE_BIG))); + } + + /** + * @hide + */ + @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING) + public boolean hasPromotableStyle() { + //TODO(b/367739672): Add progress style + return extras == null || !extras.containsKey(Notification.EXTRA_TEMPLATE) + || isStyle(Notification.BigPictureStyle.class) + || isStyle(Notification.BigTextStyle.class) + || isStyle(Notification.CallStyle.class); + } + + /** + * @hide + */ + @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING) + public boolean hasPromotableCharacteristics() { + return isColorized() + && hasTitle() + && !containsCustomViews() + && hasPromotableStyle(); + } + + /** * Whether this notification was posted by a headless system app. * * If we don't have enough information to figure this out, this will return false. Therefore, diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index d45b24ed69be..303197dfd82d 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -202,10 +202,8 @@ public class ZenModeConfig implements Parcelable { private static final int DEFAULT_CALLS_SOURCE = SOURCE_STAR; public static final String MANUAL_RULE_ID = "MANUAL_RULE"; - public static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE"; + public static final String EVENTS_OBSOLETE_RULE_ID = "EVENTS_DEFAULT_RULE"; public static final String EVERY_NIGHT_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE"; - public static final List<String> DEFAULT_RULE_IDS = Arrays.asList(EVERY_NIGHT_DEFAULT_RULE_ID, - EVENTS_DEFAULT_RULE_ID); public static final int[] ALL_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY }; @@ -424,21 +422,10 @@ public class ZenModeConfig implements Parcelable { return policy; } + @FlaggedApi(Flags.FLAG_MODES_UI) public static ZenModeConfig getDefaultConfig() { ZenModeConfig config = new ZenModeConfig(); - EventInfo eventInfo = new EventInfo(); - eventInfo.reply = REPLY_YES_OR_MAYBE; - ZenRule events = new ZenRule(); - events.id = EVENTS_DEFAULT_RULE_ID; - events.conditionId = toEventConditionId(eventInfo); - events.component = ComponentName.unflattenFromString( - "android/com.android.server.notification.EventConditionProvider"); - events.enabled = false; - events.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; - events.pkg = "android"; - config.automaticRules.put(EVENTS_DEFAULT_RULE_ID, events); - ScheduleInfo scheduleInfo = new ScheduleInfo(); scheduleInfo.days = new int[] {1, 2, 3, 4, 5, 6, 7}; scheduleInfo.startHour = 22; @@ -457,6 +444,13 @@ public class ZenModeConfig implements Parcelable { return config; } + // TODO: b/368247671 - Can be made a constant again when modes_ui is inlined + public static List<String> getDefaultRuleIds() { + return Flags.modesUi() + ? List.of(EVERY_NIGHT_DEFAULT_RULE_ID) + : List.of(EVERY_NIGHT_DEFAULT_RULE_ID, EVENTS_OBSOLETE_RULE_ID); + } + void ensureManualZenRule() { if (manualRule == null) { final ZenRule newRule = new ZenRule(); diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index e90b1c0fc167..229e8ee75844 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -26,7 +26,6 @@ import android.annotation.Nullable; import android.os.IBinder; import android.os.Trace; import android.util.proto.ProtoOutputStream; -import android.view.SurfaceControl.Transaction; import android.view.inputmethod.Flags; import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethodManager; @@ -34,8 +33,6 @@ import android.view.inputmethod.InputMethodManager; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.SoftInputShowHideReason; -import java.util.function.Supplier; - /** * Controls the visibility and animations of IME window insets source. * @hide @@ -54,10 +51,8 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { */ private boolean mIsRequestedVisibleAwaitingLeash; - public ImeInsetsSourceConsumer( - int id, InsetsState state, Supplier<Transaction> transactionSupplier, - InsetsController controller) { - super(id, WindowInsets.Type.ime(), state, transactionSupplier, controller); + public ImeInsetsSourceConsumer(int id, InsetsState state, InsetsController controller) { + super(id, WindowInsets.Type.ime(), state, controller); } @Override diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java index 04bb6091672b..a0d8a173c3e5 100644 --- a/core/java/android/view/InsetsAnimationControlCallbacks.java +++ b/core/java/android/view/InsetsAnimationControlCallbacks.java @@ -54,13 +54,6 @@ public interface InsetsAnimationControlCallbacks { void notifyFinished(InsetsAnimationControlRunner runner, boolean shown); /** - * Apply the new params to the surface. - * @param params The {@link android.view.SyncRtSurfaceTransactionApplier.SurfaceParams} to - * apply. - */ - void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params); - - /** * Post a message to release the Surface, guaranteed to happen after all * previous calls to applySurfaceParams. */ diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 91e9230cdc6a..97facc1ba472 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -99,6 +99,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro private final @InsetsType int mTypes; private @InsetsType int mControllingTypes; private final InsetsAnimationControlCallbacks mController; + private final SurfaceParamsApplier mSurfaceParamsApplier; private final WindowInsetsAnimation mAnimation; private final long mDurationMs; private final Interpolator mInterpolator; @@ -123,6 +124,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, @Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener, @InsetsType int types, InsetsAnimationControlCallbacks controller, + SurfaceParamsApplier surfaceParamsApplier, InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation, CompatibilityInfo.Translator translator, @Nullable ImeTracker.Token statsToken) { @@ -131,6 +133,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro mTypes = types; mControllingTypes = types; mController = controller; + mSurfaceParamsApplier = surfaceParamsApplier; mInitialInsetsState = new InsetsState(state, true /* copySources */); if (frame != null) { final SparseIntArray idSideMap = new SparseIntArray(); @@ -258,6 +261,11 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro } @Override + public SurfaceParamsApplier getSurfaceParamsApplier() { + return mSurfaceParamsApplier; + } + + @Override @Nullable public ImeTracker.Token getStatsToken() { return mStatsToken; @@ -305,7 +313,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro updateLeashesForSide(SIDE_RIGHT, offset.right, params, outState, mPendingAlpha); updateLeashesForSide(SIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha); - mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()])); + mSurfaceParamsApplier.applySurfaceParams(params.toArray(new SurfaceParams[params.size()])); mCurrentInsets = mPendingInsets; mAnimation.setFraction(mPendingFraction); mCurrentAlpha = mPendingAlpha; diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java index 8cb8b47dd0ec..4f102da4692a 100644 --- a/core/java/android/view/InsetsAnimationControlRunner.java +++ b/core/java/android/view/InsetsAnimationControlRunner.java @@ -77,6 +77,11 @@ public interface InsetsAnimationControlRunner { @AnimationType int getAnimationType(); /** + * @return The {@link SurfaceParamsApplier} this runner is using. + */ + SurfaceParamsApplier getSurfaceParamsApplier(); + + /** * @return The token tracking the current IME request or {@code null} otherwise. */ @Nullable @@ -99,4 +104,27 @@ public interface InsetsAnimationControlRunner { * @param fieldId FieldId of the implementation class */ void dumpDebug(ProtoOutputStream proto, long fieldId); + + /** + * Interface applying given surface operations. + */ + interface SurfaceParamsApplier { + + SurfaceParamsApplier DEFAULT = params -> { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (int i = params.length - 1; i >= 0; i--) { + SyncRtSurfaceTransactionApplier.applyParams(t, params[i], new float[9]); + } + t.apply(); + t.close(); + }; + + /** + * Apply the new params to the surface. + * + * @param params The {@link SyncRtSurfaceTransactionApplier.SurfaceParams} to apply. + */ + void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params); + + } } diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index fc185bc73735..8c2c4951a9f7 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -17,7 +17,6 @@ package android.view; import static android.view.InsetsController.DEBUG; -import static android.view.SyncRtSurfaceTransactionApplier.applyParams; import android.annotation.Nullable; import android.annotation.UiThread; @@ -30,7 +29,6 @@ import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.InsetsController.AnimationType; import android.view.InsetsController.LayoutInsetsDuringAnimation; -import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; import android.view.inputmethod.ImeTracker; @@ -50,8 +48,6 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro private final InsetsAnimationControlCallbacks mCallbacks = new InsetsAnimationControlCallbacks() { - private final float[] mTmpFloat9 = new float[9]; - @Override @UiThread public <T extends InsetsAnimationControlRunner & InternalInsetsAnimationController> @@ -81,19 +77,6 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro } @Override - public void applySurfaceParams(SurfaceParams... params) { - if (DEBUG) Log.d(TAG, "applySurfaceParams"); - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - for (int i = params.length - 1; i >= 0; i--) { - SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i]; - applyParams(t, surfaceParams, mTmpFloat9); - } - t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); - t.apply(); - t.close(); - } - - @Override public void releaseSurfaceControlFromRt(SurfaceControl sc) { if (DEBUG) Log.d(TAG, "releaseSurfaceControlFromRt"); // Since we don't push the SurfaceParams to the RT we can release directly @@ -106,6 +89,22 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro } }; + private SurfaceParamsApplier mSurfaceParamsApplier = new SurfaceParamsApplier() { + + private final float[] mTmpFloat9 = new float[9]; + + @Override + public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params) { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (int i = params.length - 1; i >= 0; i--) { + SyncRtSurfaceTransactionApplier.applyParams(t, params[i], mTmpFloat9); + } + t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); + t.apply(); + t.close(); + } + }; + @UiThread public InsetsAnimationThreadControlRunner(SparseArray<InsetsSourceControl> controls, @Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener, @@ -117,8 +116,8 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro mMainThreadHandler = mainThreadHandler; mOuterCallbacks = controller; mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types, - mCallbacks, insetsAnimationSpec, animationType, layoutInsetsDuringAnimation, - translator, statsToken); + mCallbacks, mSurfaceParamsApplier, insetsAnimationSpec, animationType, + layoutInsetsDuringAnimation, translator, statsToken); InsetsAnimationThread.getHandler().post(() -> { if (mControl.isCancelled()) { return; @@ -187,6 +186,11 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro } @Override + public SurfaceParamsApplier getSurfaceParamsApplier() { + return mSurfaceParamsApplier; + } + + @Override public void updateLayoutInsetsDuringAnimation( @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { InsetsAnimationThread.getHandler().post( diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 8fdf91a2d87c..e38281ffd020 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -55,7 +55,6 @@ import android.util.Pair; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.InsetsSourceConsumer.ShowResult; -import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; @@ -85,7 +84,8 @@ import java.util.Objects; * Implements {@link WindowInsetsController} on the client. * @hide */ -public class InsetsController implements WindowInsetsController, InsetsAnimationControlCallbacks { +public class InsetsController implements WindowInsetsController, InsetsAnimationControlCallbacks, + InsetsAnimationControlRunner.SurfaceParamsApplier { private int mTypesBeingCancelled; @@ -307,7 +307,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } /** Not running an animation. */ - @VisibleForTesting public static final int ANIMATION_TYPE_NONE = -1; /** Running animation will show insets */ @@ -317,11 +316,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public static final int ANIMATION_TYPE_HIDE = 1; /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */ - @VisibleForTesting(visibility = PACKAGE) public static final int ANIMATION_TYPE_USER = 2; /** Running animation will resize insets */ - @VisibleForTesting public static final int ANIMATION_TYPE_RESIZE = 3; @Retention(RetentionPolicy.SOURCE) @@ -757,11 +754,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public InsetsController(Host host) { this(host, (controller, id, type) -> { if (!Flags.refactorInsetsController() && type == ime()) { - return new ImeInsetsSourceConsumer(id, controller.mState, - Transaction::new, controller); + return new ImeInsetsSourceConsumer(id, controller.mState, controller); } else { - return new InsetsSourceConsumer(id, type, controller.mState, - Transaction::new, controller); + return new InsetsSourceConsumer(id, type, controller.mState, controller); } }, host.getHandler()); } @@ -1525,9 +1520,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation insetsAnimationSpec, animationType, layoutInsetsDuringAnimation, mHost.getTranslator(), mHost.getHandler(), statsToken) : new InsetsAnimationControlImpl(controls, - frame, mState, listener, typesReady, this, insetsAnimationSpec, + frame, mState, listener, typesReady, this, this, insetsAnimationSpec, animationType, layoutInsetsDuringAnimation, mHost.getTranslator(), statsToken); + for (int i = controls.size() - 1; i >= 0; i--) { + final InsetsSourceConsumer consumer = mSourceConsumers.get(controls.keyAt(i)); + if (consumer != null) { + consumer.setSurfaceParamsApplier(runner.getSurfaceParamsApplier()); + } + } if ((typesReady & WindowInsets.Type.ime()) != 0) { ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl", mHost.getInputMethodManager(), null /* icProto */); diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java index f90b8411e333..5262751cc6ed 100644 --- a/core/java/android/view/InsetsResizeAnimationRunner.java +++ b/core/java/android/view/InsetsResizeAnimationRunner.java @@ -94,6 +94,11 @@ public class InsetsResizeAnimationRunner implements InsetsAnimationControlRunner } @Override + public SurfaceParamsApplier getSurfaceParamsApplier() { + return SurfaceParamsApplier.DEFAULT; + } + + @Override @Nullable public ImeTracker.Token getStatsToken() { // Return null as resizing the IME view is not explicitly tracked. diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 391d757365e6..2e2ff1d49dfe 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -17,6 +17,7 @@ package android.view; import static android.view.InsetsController.ANIMATION_TYPE_NONE; +import static android.view.InsetsController.ANIMATION_TYPE_RESIZE; import static android.view.InsetsController.AnimationType; import static android.view.InsetsController.DEBUG; import static android.view.InsetsSourceConsumerProto.ANIMATION_STATE; @@ -32,12 +33,13 @@ import static com.android.window.flags.Flags.insetsControlSeq; import android.annotation.IntDef; import android.annotation.Nullable; +import android.graphics.Matrix; +import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.text.TextUtils; import android.util.Log; import android.util.proto.ProtoOutputStream; -import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetsType; import android.view.inputmethod.Flags; import android.view.inputmethod.ImeTracker; @@ -48,7 +50,6 @@ import com.android.internal.inputmethod.ImeTracing; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; -import java.util.function.Supplier; /** * Controls the visibility and animations of a single window insets source. @@ -92,10 +93,12 @@ public class InsetsSourceConsumer { private final int mType; private static final String TAG = "InsetsSourceConsumer"; - private final Supplier<Transaction> mTransactionSupplier; @Nullable private InsetsSourceControl mSourceControl; private boolean mHasWindowFocus; + private InsetsAnimationControlRunner.SurfaceParamsApplier mSurfaceParamsApplier = + InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT; + private final Matrix mTmpMatrix = new Matrix(); /** * Whether the view has focus returned by {@link #onWindowFocusGained(boolean)}. @@ -108,16 +111,13 @@ public class InsetsSourceConsumer { * @param id The ID of the consumed insets. * @param type The {@link InsetsType} of the consumed insets. * @param state The current {@link InsetsState} of the consumed insets. - * @param transactionSupplier The source of new {@link Transaction} instances. The supplier - * must provide *new* instances, which will be explicitly closed by this class. * @param controller The {@link InsetsController} to use for insets interaction. */ public InsetsSourceConsumer(int id, @InsetsType int type, InsetsState state, - Supplier<Transaction> transactionSupplier, InsetsController controller) { + InsetsController controller) { mId = id; mType = type; mState = state; - mTransactionSupplier = transactionSupplier; mController = controller; } @@ -162,6 +162,9 @@ public class InsetsSourceConsumer { if (localVisible != serverVisible) { mController.notifyVisibilityChanged(); } + + // Reset the applier to the default one which has the most lightweight implementation. + setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT); } else { final boolean requestedVisible = isRequestedVisibleAwaitingControl(); final SurfaceControl oldLeash = lastControl != null ? lastControl.getLeash() : null; @@ -184,10 +187,11 @@ public class InsetsSourceConsumer { mController.notifyVisibilityChanged(); } - // If we have a new leash, make sure visibility is up-to-date, even though we - // didn't want to run an animation above. - if (mController.getAnimationType(mType) == ANIMATION_TYPE_NONE) { - applyRequestedVisibilityToControl(); + // If there is no animation controlling the leash, make sure the visibility and the + // position is up-to-date. Note: ANIMATION_TYPE_RESIZE doesn't control the leash. + final int animType = mController.getAnimationType(mType); + if (animType == ANIMATION_TYPE_NONE || animType == ANIMATION_TYPE_RESIZE) { + applyRequestedVisibilityAndPositionToControl(); } // Remove the surface that owned by last control when it lost. @@ -229,6 +233,15 @@ public class InsetsSourceConsumer { } /** + * Sets the SurfaceParamsApplier that the latest animation runner is using. The leash owned by + * this class is always applied by the applier, so that the transaction order can always be + * aligned with the calling sequence. + */ + void setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier applier) { + mSurfaceParamsApplier = applier; + } + + /** * Called right after the animation is started or finished. */ @VisibleForTesting(visibility = PACKAGE) @@ -431,24 +444,30 @@ public class InsetsSourceConsumer { if (DEBUG) Log.d(TAG, "updateSource: " + newSource); } - private void applyRequestedVisibilityToControl() { - if (mSourceControl == null || mSourceControl.getLeash() == null) { + private void applyRequestedVisibilityAndPositionToControl() { + if (mSourceControl == null) { return; } - - final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0; - try (Transaction t = mTransactionSupplier.get()) { - if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + requestedVisible); - if (requestedVisible) { - t.show(mSourceControl.getLeash()); - } else { - t.hide(mSourceControl.getLeash()); - } - // Ensure the alpha value is aligned with the actual requested visibility. - t.setAlpha(mSourceControl.getLeash(), requestedVisible ? 1 : 0); - t.apply(); + final SurfaceControl leash = mSourceControl.getLeash(); + if (leash == null) { + return; } - onPerceptible(requestedVisible); + + final boolean visible = (mController.getRequestedVisibleTypes() & mType) != 0; + final Point surfacePosition = mSourceControl.getSurfacePosition(); + + if (DEBUG) Log.d(TAG, "applyRequestedVisibilityAndPositionToControl: visible=" + visible + + " position=" + surfacePosition); + + mTmpMatrix.setTranslate(surfacePosition.x, surfacePosition.y); + mSurfaceParamsApplier.applySurfaceParams( + new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(leash) + .withVisibility(visible) + .withAlpha(visible ? 1 : 0) + .withMatrix(mTmpMatrix) + .build()); + + onPerceptible(visible); } void dumpDebug(ProtoOutputStream proto, long fieldId) { diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index e1402f8224eb..b6aad1145880 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -266,3 +266,14 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "remove_starting_window_wait_for_multi_transitions" + namespace: "windowing_frontend" + description: "Avoid remove starting window too early when playing multiple transitions" + bug: "362347290" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index fbc058cc0330..b0e38e256430 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -122,18 +122,20 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto private final Lock mBackgroundServiceLock = new ReentrantLock(); private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor(); - public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups) { + public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups) + throws ServiceManager.ServiceNotFoundException { this(null, null, null, () -> {}, groups); } - public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups) { + public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups) + throws ServiceManager.ServiceNotFoundException { this(null, null, null, cacheUpdater, groups); } public PerfettoProtoLogImpl( @NonNull String viewerConfigFilePath, @NonNull Runnable cacheUpdater, - @NonNull IProtoLogGroup[] groups) { + @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException { this(viewerConfigFilePath, null, new ProtoLogViewerConfigReader(() -> { @@ -177,12 +179,14 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, @Nullable ProtoLogViewerConfigReader viewerConfigReader, @NonNull Runnable cacheUpdater, - @NonNull IProtoLogGroup[] groups) { + @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException { this(viewerConfigFilePath, viewerConfigInputStreamProvider, viewerConfigReader, cacheUpdater, groups, ProtoLogDataSource::new, - IProtoLogConfigurationService.Stub - .asInterface(ServiceManager.getService(PROTOLOG_CONFIGURATION_SERVICE)) + android.tracing.Flags.clientSideProtoLogging() ? + IProtoLogConfigurationService.Stub.asInterface( + ServiceManager.getServiceOrThrow(PROTOLOG_CONFIGURATION_SERVICE) + ) : null ); } @@ -222,7 +226,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto if (android.tracing.Flags.clientSideProtoLogging()) { mProtoLogConfigurationService = configurationService; Objects.requireNonNull(mProtoLogConfigurationService, - "ServiceManager returned a null ProtoLog Configuration Service"); + "A null ProtoLog Configuration Service was provided!"); try { var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs(); diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java index bf77db7b6a33..adf03fe5f775 100644 --- a/core/java/com/android/internal/protolog/ProtoLog.java +++ b/core/java/com/android/internal/protolog/ProtoLog.java @@ -16,6 +16,8 @@ package com.android.internal.protolog; +import android.os.ServiceManager; + import com.android.internal.protolog.common.IProtoLog; import com.android.internal.protolog.common.IProtoLogGroup; import com.android.internal.protolog.common.LogLevel; @@ -76,7 +78,11 @@ public class ProtoLog { groups = allGroups.toArray(new IProtoLogGroup[0]); } - sProtoLogInstance = new PerfettoProtoLogImpl(groups); + try { + sProtoLogInstance = new PerfettoProtoLogImpl(groups); + } catch (ServiceManager.ServiceNotFoundException e) { + throw new RuntimeException(e); + } } } else { sProtoLogInstance = new LogcatOnlyProtoLogImpl(); diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java index 7bdcf2d14b19..5d67534b1b44 100644 --- a/core/java/com/android/internal/protolog/ProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java @@ -23,6 +23,7 @@ import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LO import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH; import android.annotation.Nullable; +import android.os.ServiceManager; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -106,18 +107,23 @@ public class ProtoLogImpl { final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]); if (android.tracing.Flags.perfettoProtologTracing()) { - File f = new File(sViewerConfigPath); - if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) { - // TODO(b/353530422): Remove - temporary fix to unblock b/352290057 - // In some tests the viewer config file might not exist in which we don't - // want to provide config path to the user - Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up " - + ProtoLogImpl.class.getSimpleName() + ". " - + "Setting up without a viewer config instead..."); - sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups); - } else { - sServiceInstance = - new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups); + try { + File f = new File(sViewerConfigPath); + if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) { + // TODO(b/353530422): Remove - temporary fix to unblock b/352290057 + // In some tests the viewer config file might not exist in which we don't + // want to provide config path to the user + Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up " + + ProtoLogImpl.class.getSimpleName() + ". " + + "Setting up without a viewer config instead..."); + + sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups); + } else { + sServiceInstance = + new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups); + } + } catch (ServiceManager.ServiceNotFoundException e) { + throw new RuntimeException(e); } } else { var protologImpl = new LegacyProtoLogImpl( diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 0837b458c3ba..0f73df92ca93 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -37,6 +37,7 @@ import static android.app.Notification.EXTRA_PICTURE; import static android.app.Notification.EXTRA_PICTURE_ICON; import static android.app.Notification.EXTRA_SUMMARY_TEXT; import static android.app.Notification.EXTRA_TITLE; +import static android.app.Notification.FLAG_CAN_COLORIZE; import static android.app.Notification.GROUP_ALERT_CHILDREN; import static android.app.Notification.GROUP_ALERT_SUMMARY; import static android.app.Notification.GROUP_KEY_SILENT; @@ -96,6 +97,7 @@ import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.style.TextAppearanceSpan; import android.util.Pair; +import android.util.Slog; import android.widget.RemoteViews; import androidx.test.InstrumentationRegistry; @@ -126,6 +128,8 @@ public class NotificationTest { private Context mContext; + private RemoteViews mRemoteViews; + @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -133,23 +137,25 @@ public class NotificationTest { @Before public void setUp() { mContext = InstrumentationRegistry.getContext(); + mRemoteViews = new RemoteViews( + mContext.getPackageName(), R.layout.notification_template_header); } @Test public void testColorizedByPermission() { Notification n = new Notification.Builder(mContext, "test") - .setFlag(Notification.FLAG_CAN_COLORIZE, true) + .setFlag(FLAG_CAN_COLORIZE, true) .setColorized(true).setColor(Color.WHITE) .build(); assertTrue(n.isColorized()); n = new Notification.Builder(mContext, "test") - .setFlag(Notification.FLAG_CAN_COLORIZE, true) + .setFlag(FLAG_CAN_COLORIZE, true) .build(); assertFalse(n.isColorized()); n = new Notification.Builder(mContext, "test") - .setFlag(Notification.FLAG_CAN_COLORIZE, false) + .setFlag(FLAG_CAN_COLORIZE, false) .setColorized(true).setColor(Color.WHITE) .build(); assertFalse(n.isColorized()); @@ -215,6 +221,275 @@ public class NotificationTest { } @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasTitle_noStyle() { + Notification n = new Notification.Builder(mContext, "test") + .setContentTitle("TITLE") + .build(); + assertThat(n.hasTitle()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasTitle_bigText() { + Notification n = new Notification.Builder(mContext, "test") + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .build(); + assertThat(n.hasTitle()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasTitle_noTitle() { + Notification n = new Notification.Builder(mContext, "test") + .setContentText("text not title") + .build(); + assertThat(n.hasTitle()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testContainsCustomViews_none() { + Notification np = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("test") + .build(); + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("test") + .setPublicVersion(np) + .build(); + assertThat(n.containsCustomViews()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testContainsCustomViews_content() { + Notification np = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("test") + .build(); + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("test") + .setCustomContentView(mRemoteViews) + .setPublicVersion(np) + .build(); + assertThat(n.containsCustomViews()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testContainsCustomViews_big() { + Notification np = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("test") + .build(); + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("test") + .setCustomBigContentView(mRemoteViews) + .setPublicVersion(np) + .build(); + assertThat(n.containsCustomViews()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testContainsCustomViews_headsUp() { + Notification np = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("test") + .build(); + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("test") + .setCustomHeadsUpContentView(mRemoteViews) + .setPublicVersion(np) + .build(); + assertThat(n.containsCustomViews()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testContainsCustomViews_content_public() { + Notification np = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("public") + .setCustomContentView(mRemoteViews) + .build(); + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("test") + .setPublicVersion(np) + .build(); + assertThat(n.containsCustomViews()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testContainsCustomViews_big_public() { + Notification np = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("test") + .setCustomBigContentView(mRemoteViews) + .build(); + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("test") + .setPublicVersion(np) + .build(); + assertThat(n.containsCustomViews()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testContainsCustomViews_headsUp_public() { + Notification np = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("test") + .setCustomHeadsUpContentView(mRemoteViews) + .build(); + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("test") + .setPublicVersion(np) + .build(); + assertThat(n.containsCustomViews()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasPromotableStyle_noStyle() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentText("test") + .build(); + assertThat(n.hasPromotableStyle()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasPromotableStyle_bigPicture() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigPictureStyle()) + .build(); + assertThat(n.hasPromotableStyle()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasPromotableStyle_bigText() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle()) + .build(); + assertThat(n.hasPromotableStyle()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasPromotableStyle_no_messagingStyle() { + Notification.MessagingStyle style = new Notification.MessagingStyle("self name") + .setGroupConversation(true) + .setConversationTitle("test conversation title"); + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(style) + .build(); + assertThat(n.hasPromotableStyle()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasPromotableStyle_no_mediaStyle() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.MediaStyle()) + .build(); + assertThat(n.hasPromotableStyle()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasPromotableStyle_no_inboxStyle() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.InboxStyle()) + .build(); + assertThat(n.hasPromotableStyle()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasPromotableStyle_callText() { + PendingIntent answerIntent = createPendingIntent("answer"); + PendingIntent declineIntent = createPendingIntent("decline"); + Notification.CallStyle style = Notification.CallStyle.forIncomingCall( + new Person.Builder().setName("A Caller").build(), + declineIntent, + answerIntent + ); + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(style) + .build(); + assertThat(n.hasPromotableStyle()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasPromotableCharacteristics() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_CAN_COLORIZE, true) + .build(); + assertThat(n.hasPromotableCharacteristics()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasPromotableCharacteristics_wrongStyle() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.InboxStyle()) + .setContentTitle("TITLE") + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_CAN_COLORIZE, true) + .build(); + assertThat(n.hasPromotableCharacteristics()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasPromotableCharacteristics_notColorized() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .build(); + assertThat(n.hasPromotableCharacteristics()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasPromotableCharacteristics_noTitle() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle()) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_CAN_COLORIZE, true) + .build(); + assertThat(n.hasPromotableCharacteristics()).isFalse(); + } + + @Test @EnableFlags(Flags.FLAG_API_RICH_ONGOING) public void testGetShortCriticalText_noneSet() { Notification n = new Notification.Builder(mContext, "test") diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index 786f1e84728d..ba6f62c6ed19 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -38,7 +38,6 @@ import android.graphics.Rect; import android.graphics.RectF; import android.platform.test.annotations.Presubmit; import android.util.SparseArray; -import android.view.SurfaceControl.Transaction; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; @@ -79,7 +78,6 @@ public class InsetsAnimationControlImplTest { private SurfaceControl mNavLeash; private InsetsState mInsetsState; - @Mock Transaction mMockTransaction; @Mock InsetsController mMockController; @Mock WindowInsetsAnimationControlListener mMockListener; @@ -98,16 +96,14 @@ public class InsetsAnimationControlImplTest { mInsetsState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars()) .setFrame(new Rect(400, 0, 500, 500)); InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ID_STATUS_BAR, - WindowInsets.Type.statusBars(), mInsetsState, - () -> mMockTransaction, mMockController); + WindowInsets.Type.statusBars(), mInsetsState, mMockController); topConsumer.setControl( new InsetsSourceControl(ID_STATUS_BAR, WindowInsets.Type.statusBars(), mStatusLeash, true, new Point(0, 0), Insets.of(0, 100, 0, 0)), new int[1], new int[1]); InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ID_NAVIGATION_BAR, - WindowInsets.Type.navigationBars(), mInsetsState, - () -> mMockTransaction, mMockController); + WindowInsets.Type.navigationBars(), mInsetsState, mMockController); navConsumer.setControl( new InsetsSourceControl(ID_NAVIGATION_BAR, WindowInsets.Type.navigationBars(), mNavLeash, true, new Point(400, 0), Insets.of(0, 0, 100, 0)), @@ -131,8 +127,9 @@ public class InsetsAnimationControlImplTest { mController = new InsetsAnimationControlImpl(controls, new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(), - mMockController, spec /* insetsAnimationSpecCreator */, 0 /* animationType */, - 0 /* layoutInsetsDuringAnimation */, null /* translator */, null /* statsToken */); + mMockController, mMockController, spec /* insetsAnimationSpecCreator */, + 0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */, + null /* statsToken */); mController.setReadyDispatched(true); } diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index bec8b1f76394..4516e9ce72fc 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -63,7 +63,6 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.CancellationSignal; import android.platform.test.annotations.Presubmit; -import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsController.OnControllableInsetsChangedListener; import android.view.WindowManager.BadTokenException; @@ -138,8 +137,7 @@ public class InsetsControllerTest { mTestHost = spy(new TestHost(mViewRoot)); mController = new InsetsController(mTestHost, (controller, id, type) -> { if (!Flags.refactorInsetsController() && type == ime()) { - return new InsetsSourceConsumer(id, type, controller.getState(), - Transaction::new, controller) { + return new InsetsSourceConsumer(id, type, controller.getState(), controller) { private boolean mImeRequestedShow; @@ -155,8 +153,7 @@ public class InsetsControllerTest { } }; } else { - return new InsetsSourceConsumer(id, type, controller.getState(), - Transaction::new, controller); + return new InsetsSourceConsumer(id, type, controller.getState(), controller); } }, mTestHandler); final Rect rect = new Rect(5, 5, 5, 5); diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java index 655cb4519d3c..d6d45e839f2f 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java @@ -28,10 +28,7 @@ import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; import android.app.Instrumentation; import android.content.Context; @@ -39,7 +36,6 @@ import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; -import android.view.SurfaceControl.Transaction; import android.view.WindowManager.BadTokenException; import android.view.WindowManager.LayoutParams; import android.view.inputmethod.ImeTracker; @@ -51,7 +47,6 @@ import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -75,9 +70,9 @@ public class InsetsSourceConsumerTest { private SurfaceSession mSession = new SurfaceSession(); private SurfaceControl mLeash; - @Mock Transaction mMockTransaction; private InsetsSource mSpyInsetsSource; private boolean mRemoveSurfaceCalled = false; + private boolean mSurfaceParamsApplied = false; private InsetsController mController; private InsetsState mState; private ViewRootImpl mViewRoot; @@ -102,9 +97,14 @@ public class InsetsSourceConsumerTest { mSpyInsetsSource = Mockito.spy(new InsetsSource(ID_STATUS_BAR, statusBars())); mState.addSource(mSpyInsetsSource); - mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot)); - mConsumer = new InsetsSourceConsumer(ID_STATUS_BAR, statusBars(), mState, - () -> mMockTransaction, mController) { + mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot)) { + @Override + public void applySurfaceParams( + final SyncRtSurfaceTransactionApplier.SurfaceParams... params) { + mSurfaceParamsApplied = true; + } + }; + mConsumer = new InsetsSourceConsumer(ID_STATUS_BAR, statusBars(), mState, mController) { @Override public void removeSurface() { super.removeSurface(); @@ -148,8 +148,7 @@ public class InsetsSourceConsumerTest { InsetsState state = new InsetsState(); InsetsController controller = new InsetsController(new ViewRootInsetsControllerHost( mViewRoot)); - InsetsSourceConsumer consumer = new InsetsSourceConsumer( - ID_IME, ime(), state, null, controller); + InsetsSourceConsumer consumer = new InsetsSourceConsumer(ID_IME, ime(), state, controller); InsetsSource source = new InsetsSource(ID_IME, ime()); source.setFrame(0, 1, 2, 3); @@ -182,9 +181,9 @@ public class InsetsSourceConsumerTest { public void testRestore() { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mConsumer.setControl(null, new int[1], new int[1]); - reset(mMockTransaction); + mSurfaceParamsApplied = false; mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars()); - verifyZeroInteractions(mMockTransaction); + assertFalse(mSurfaceParamsApplied); int[] hideTypes = new int[1]; mConsumer.setControl( new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash, @@ -200,8 +199,9 @@ public class InsetsSourceConsumerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars()); mConsumer.setControl(null, new int[1], new int[1]); - reset(mMockTransaction); - verifyZeroInteractions(mMockTransaction); + mLeash = new SurfaceControl.Builder(mSession) + .setName("testSurface") + .build(); mRemoveSurfaceCalled = false; int[] hideTypes = new int[1]; mConsumer.setControl( @@ -221,8 +221,7 @@ public class InsetsSourceConsumerTest { ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot); InsetsController insetsController = new InsetsController(host, (ic, id, type) -> { if (type == ime()) { - return new InsetsSourceConsumer(ID_IME, ime(), state, - () -> mMockTransaction, ic) { + return new InsetsSourceConsumer(ID_IME, ime(), state, ic) { @Override public int requestShow(boolean fromController, ImeTracker.Token statsToken) { @@ -230,14 +229,14 @@ public class InsetsSourceConsumerTest { } }; } - return new InsetsSourceConsumer(id, type, ic.getState(), Transaction::new, ic); + return new InsetsSourceConsumer(id, type, ic.getState(), ic); }, host.getHandler()); InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ID_IME, ime()); // Initial IME insets source control with its leash. imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash, false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]); - reset(mMockTransaction); + mSurfaceParamsApplied = false; // Verify when the app requests controlling show IME animation, the IME leash // visibility won't be updated when the consumer received the same leash in setControl. @@ -246,7 +245,7 @@ public class InsetsSourceConsumerTest { assertEquals(ANIMATION_TYPE_USER, insetsController.getAnimationType(ime())); imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash, true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]); - verify(mMockTransaction, never()).show(mLeash); + assertFalse(mSurfaceParamsApplied); }); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 7e6f43458ba6..4607a8ec1210 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -584,7 +584,8 @@ public class ShellTaskOrganizer extends TaskOrganizer { final boolean windowModeChanged = data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode(); final boolean visibilityChanged = data.getTaskInfo().isVisible != taskInfo.isVisible; - if (windowModeChanged || visibilityChanged) { + if (windowModeChanged || (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM + && visibilityChanged)) { mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskRunningInfoChanged(taskInfo)); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index e514dc38208e..f01ed84adc74 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -39,6 +39,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.never; import android.app.ActivityManager.RunningTaskInfo; import android.app.TaskInfo; @@ -599,6 +600,18 @@ public class ShellTaskOrganizerTests extends ShellTestCase { } @Test + public void testRecentTasks_visibilityChanges_notFreeForm_shouldNotNotifyTaskController() { + RunningTaskInfo task1_visible = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN); + mOrganizer.onTaskAppeared(task1_visible, /* leash= */ null); + RunningTaskInfo task1_hidden = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN); + task1_hidden.isVisible = false; + + mOrganizer.onTaskInfoChanged(task1_hidden); + + verify(mRecentTasksController, never()).onTaskRunningInfoChanged(task1_hidden); + } + + @Test public void testRecentTasks_windowingModeChanges_shouldNotifyTaskController() { RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN); mOrganizer.onTaskAppeared(task1, /* leash= */ null); diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig index bf6ec9635912..185f579df4b9 100644 --- a/media/java/android/media/flags/editing.aconfig +++ b/media/java/android/media/flags/editing.aconfig @@ -8,3 +8,10 @@ flag { description: "Add media metrics for transcoding/editing events." bug: "297487694" } + +flag { + name: "stagefrightrecorder_enable_b_frames" + namespace: "media_solutions" + description: "Enable B frames for Stagefright recorder." + bug: "341121900" +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt index 28db3b861278..f90f02aad892 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt @@ -70,7 +70,14 @@ constructor( ) { private val keyguardOccludedByApp: Flow<Boolean> = if (KeyguardWmStateRefactor.isEnabled) { - keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.OCCLUDED } + combine( + keyguardTransitionInteractor.currentKeyguardState, + communalSceneInteractor.isIdleOnCommunal, + ::Pair, + ) + .map { (currentState, isIdleOnCommunal) -> + currentState == KeyguardState.OCCLUDED && !isIdleOnCommunal + } } else { combine( keyguardInteractor.isKeyguardOccluded, @@ -120,7 +127,7 @@ constructor( // On fingerprint success when the screen is on and not dreaming, go to the home screen fingerprintUnlockSuccessEvents .sample( - combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair), + combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair) ) .collect { (interactive, dreaming) -> if (interactive && !dreaming) { @@ -148,7 +155,7 @@ constructor( } }, /* cancel= */ null, - /* afterKeyguardGone */ false + /* afterKeyguardGone */ false, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt index 4b62eab08775..0d55709e94d6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt @@ -179,14 +179,6 @@ constructor( } else { button(KeyguardQuickAffordancePosition.BOTTOM_START) } - .stateIn( - scope = applicationScope, - started = SharingStarted.Eagerly, - initialValue = - KeyguardQuickAffordanceViewModel( - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId() - ), - ) /** An observable for the view-model of the "end button" quick affordance. */ val endButton: Flow<KeyguardQuickAffordanceViewModel> = @@ -200,14 +192,6 @@ constructor( } else { button(KeyguardQuickAffordancePosition.BOTTOM_END) } - .stateIn( - scope = applicationScope, - started = SharingStarted.Eagerly, - initialValue = - KeyguardQuickAffordanceViewModel( - slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId() - ), - ) /** * Notifies that a slot with the given ID has been selected in the preview experience that is diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 560028cb5640..7b6a2cb62b14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -444,11 +444,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView if (onFinishedRunnable != null) { onFinishedRunnable.run(); } - if (mRunWithoutInterruptions) { - enableAppearDrawing(false); - } // We need to reset the View state, even if the animation was cancelled + enableAppearDrawing(false); onAppearAnimationFinished(isAppearing); if (mRunWithoutInterruptions) { diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java index 1f98334bb8ce..c3b7087a44c3 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java @@ -16,15 +16,7 @@ package com.android.server.appfunctions; -import android.annotation.NonNull; -import android.os.UserHandle; -import android.util.SparseArray; - -import com.android.internal.annotations.GuardedBy; - import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -41,50 +33,5 @@ public final class AppFunctionExecutors { /* unit= */ TimeUnit.SECONDS, /* workQueue= */ new LinkedBlockingQueue<>()); - /** A map of per-user executors for queued work. */ - @GuardedBy("sLock") - private static final SparseArray<ExecutorService> mPerUserExecutorsLocked = new SparseArray<>(); - - private static final Object sLock = new Object(); - - /** - * Returns a per-user executor for queued metadata sync request. - * - * <p>The work submitted to these executor (Sync request) needs to be synchronous per user hence - * the use of a single thread. - * - * <p>Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code - * MetadataSyncAdapter}. - */ - // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself. - public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) { - synchronized (sLock) { - ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null); - if (executor == null) { - executor = Executors.newSingleThreadExecutor(); - mPerUserExecutorsLocked.put(user.getIdentifier(), executor); - } - return executor; - } - } - - /** - * Shuts down and removes the per-user executor for queued work. - * - * <p>This should be called when the user is removed. - */ - public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user) - throws InterruptedException { - ExecutorService executor; - synchronized (sLock) { - executor = mPerUserExecutorsLocked.get(user.getIdentifier()); - mPerUserExecutorsLocked.remove(user.getIdentifier()); - } - if (executor != null) { - executor.shutdown(); - var unused = executor.awaitTermination(30, TimeUnit.SECONDS); - } - } - private AppFunctionExecutors() {} } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index b4713d9f11af..1e723b5a1da2 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -95,12 +95,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { public void onUserStopping(@NonNull TargetUser user) { Objects.requireNonNull(user); - try { - AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle()); - MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle()); - } catch (InterruptedException e) { - Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e); - } + MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle()); } @Override diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java index e29b6e403f2a..d84b20556053 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java +++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java @@ -42,6 +42,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults; @@ -53,7 +54,9 @@ import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; /** * This class implements helper methods for synchronously interacting with AppSearch while @@ -63,9 +66,15 @@ import java.util.concurrent.Executor; */ public class MetadataSyncAdapter { private static final String TAG = MetadataSyncAdapter.class.getSimpleName(); - private final Executor mSyncExecutor; + + private final ExecutorService mExecutor; + private final AppSearchManager mAppSearchManager; private final PackageManager mPackageManager; + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private Future<?> mCurrentSyncTask; // Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility // by permissions. @@ -73,12 +82,10 @@ public class MetadataSyncAdapter { public static final int EXECUTE_APP_FUNCTIONS_TRUSTED = 10; public MetadataSyncAdapter( - @NonNull Executor syncExecutor, - @NonNull PackageManager packageManager, - @NonNull AppSearchManager appSearchManager) { - mSyncExecutor = Objects.requireNonNull(syncExecutor); + @NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) { mPackageManager = Objects.requireNonNull(packageManager); mAppSearchManager = Objects.requireNonNull(appSearchManager); + mExecutor = Executors.newSingleThreadExecutor(); } /** @@ -97,7 +104,7 @@ public class MetadataSyncAdapter { AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB) .build(); AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>(); - mSyncExecutor.execute( + Runnable runnable = () -> { try (FutureAppSearchSession staticMetadataSearchSession = new FutureAppSearchSessionImpl( @@ -117,10 +124,23 @@ public class MetadataSyncAdapter { } catch (Exception ex) { settableSyncStatus.completeExceptionally(ex); } - }); + }; + + synchronized (mLock) { + if (mCurrentSyncTask != null && !mCurrentSyncTask.isDone()) { + var unused = mCurrentSyncTask.cancel(false); + } + mCurrentSyncTask = mExecutor.submit(runnable); + } + return settableSyncStatus; } + /** This method shuts down the {@link MetadataSyncAdapter} scheduler. */ + public void shutDown() { + mExecutor.shutdown(); + } + @WorkerThread @VisibleForTesting void trySyncAppFunctionMetadataBlocking( diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java index f421527e72d0..e933ec1ba4b1 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java +++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java @@ -55,10 +55,7 @@ public final class MetadataSyncPerUser { PackageManager perUserPackageManager = userContext.getPackageManager(); if (perUserAppSearchManager != null) { metadataSyncAdapter = - new MetadataSyncAdapter( - AppFunctionExecutors.getPerUserSyncExecutor(user), - perUserPackageManager, - perUserAppSearchManager); + new MetadataSyncAdapter(perUserPackageManager, perUserAppSearchManager); sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter); return metadataSyncAdapter; } @@ -74,7 +71,12 @@ public final class MetadataSyncPerUser { */ public static void removeUserSyncAdapter(UserHandle user) { synchronized (sLock) { - sPerUserMetadataSyncAdapter.remove(user.getIdentifier()); + MetadataSyncAdapter metadataSyncAdapter = + sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null); + if (metadataSyncAdapter != null) { + metadataSyncAdapter.shutDown(); + sPerUserMetadataSyncAdapter.remove(user.getIdentifier()); + } } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ba7d4d218ca5..b15fcc917588 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -33,6 +33,7 @@ import static android.app.Notification.EXTRA_LARGE_ICON_BIG; import static android.app.Notification.EXTRA_SUB_TEXT; import static android.app.Notification.EXTRA_TEXT; import static android.app.Notification.EXTRA_TEXT_LINES; +import static android.app.Notification.EXTRA_TITLE; import static android.app.Notification.EXTRA_TITLE_BIG; import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY; import static android.app.Notification.FLAG_AUTO_CANCEL; @@ -45,6 +46,7 @@ import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_NO_DISMISS; import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; +import static android.app.Notification.FLAG_PROMOTED_ONGOING; import static android.app.Notification.FLAG_USER_INITIATED_JOB; import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT; import static android.app.NotificationChannel.NEWS_ID; @@ -3516,7 +3518,7 @@ public class NotificationManagerService extends SystemService { private String getHistoryTitle(Notification n) { CharSequence title = null; if (n.extras != null) { - title = n.extras.getCharSequence(Notification.EXTRA_TITLE); + title = n.extras.getCharSequence(EXTRA_TITLE); if (title == null) { title = n.extras.getCharSequence(EXTRA_TITLE_BIG); } @@ -4114,6 +4116,75 @@ public class NotificationManagerService extends SystemService { } @Override + @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING) + public boolean canBePromoted(String pkg, int uid) { + checkCallerIsSystemOrSystemUiOrShell(); + if (!android.app.Flags.uiRichOngoing()) { + return false; + } + return mPreferencesHelper.canBePromoted(pkg, uid); + } + + @Override + @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING) + public void setCanBePromoted(String pkg, int uid, boolean promote) { + checkCallerIsSystemOrSystemUiOrShell(); + if (!android.app.Flags.uiRichOngoing()) { + return; + } + boolean changed = mPreferencesHelper.setCanBePromoted(pkg, uid, promote); + if (changed) { + // check for pending/posted notifs from this app and update the flag + synchronized (mNotificationLock) { + // for enqueued we just need to update the flag + List<NotificationRecord> enqueued = findAppNotificationByListLocked( + mEnqueuedNotifications, pkg, UserHandle.getUserId(uid)); + for (NotificationRecord r : enqueued) { + if (promote + && r.getNotification().hasPromotableCharacteristics() + && r.getImportance() > IMPORTANCE_MIN) { + r.getNotification().flags |= FLAG_PROMOTED_ONGOING; + } else if (!promote) { + r.getNotification().flags &= ~FLAG_PROMOTED_ONGOING; + } + } + // if the notification is posted we need to update the flag and tell listeners + List<NotificationRecord> posted = findAppNotificationByListLocked( + mNotificationList, pkg, UserHandle.getUserId(uid)); + for (NotificationRecord r : posted) { + if (promote + && !hasFlag(r.getNotification().flags, FLAG_PROMOTED_ONGOING) + && r.getNotification().hasPromotableCharacteristics() + && r.getImportance() > IMPORTANCE_MIN) { + r.getNotification().flags |= FLAG_PROMOTED_ONGOING; + // we could set a wake lock here but this value should only change + // in response to user action, so the device should be awake long enough + // to post + PostNotificationTracker tracker = + mPostNotificationTrackerFactory.newTracker(null); + // Set false for isAppForeground because that field is only used + // for bubbles and messagingstyle can not be promoted + mHandler.post(new EnqueueNotificationRunnable( + r.getUser().getIdentifier(), + r, /* isAppForeground */ false, /* isAppProvided= */ false, + tracker)); + } else if (!promote + && hasFlag(r.getNotification().flags, FLAG_PROMOTED_ONGOING)){ + r.getNotification().flags &= ~FLAG_PROMOTED_ONGOING; + PostNotificationTracker tracker = + mPostNotificationTrackerFactory.newTracker(null); + mHandler.post(new EnqueueNotificationRunnable( + r.getUser().getIdentifier(), + r, /* isAppForeground */ false, /* isAppProvided= */ false, + tracker)); + } + } + } + handleSavePolicyFile(); + } + } + + @Override public boolean hasSentValidMsg(String pkg, int uid) { checkCallerIsSystem(); return mPreferencesHelper.hasSentValidMsg(pkg, uid); @@ -7698,6 +7769,16 @@ public class NotificationManagerService extends SystemService { return false; } + if (android.app.Flags.uiRichOngoing()) { + // This would normally be done in fixNotification(), but we need the channel info so + // it's done a little late + if (mPreferencesHelper.canBePromoted(pkg, notificationUid) + && notification.hasPromotableCharacteristics() + && channel.getImportance() > IMPORTANCE_MIN) { + notification.flags |= FLAG_PROMOTED_ONGOING; + } + } + final NotificationRecord r = new NotificationRecord(getContext(), n, channel); r.setIsAppImportanceLocked(mPermissionHelper.isPermissionUserSet(pkg, userId)); r.setPostSilently(postSilently); @@ -7938,6 +8019,9 @@ public class NotificationManagerService extends SystemService { } } + // Apps cannot set this flag + notification.flags &= ~FLAG_PROMOTED_ONGOING; + // Ensure CallStyle has all the correct actions if (notification.isStyle(Notification.CallStyle.class)) { Notification.Builder builder = @@ -8061,12 +8145,7 @@ public class NotificationManagerService extends SystemService { private void checkRemoteViews(String pkg, String tag, int id, Notification notification) { if (android.app.Flags.removeRemoteViews()) { - if (notification.contentView != null || notification.bigContentView != null - || notification.headsUpContentView != null - || (notification.publicVersion != null - && (notification.publicVersion.contentView != null - || notification.publicVersion.bigContentView != null - || notification.publicVersion.headsUpContentView != null))) { + if (notification.containsCustomViews()) { Slog.i(TAG, "Removed customViews for " + pkg); mUsageStats.registerImageRemoved(pkg); } @@ -9236,8 +9315,8 @@ public class NotificationManagerService extends SystemService { } } - final String oldTitle = String.valueOf(oldN.extras.get(Notification.EXTRA_TITLE)); - final String newTitle = String.valueOf(newN.extras.get(Notification.EXTRA_TITLE)); + final String oldTitle = String.valueOf(oldN.extras.get(EXTRA_TITLE)); + final String newTitle = String.valueOf(newN.extras.get(EXTRA_TITLE)); if (!Objects.equals(oldTitle, newTitle)) { if (DEBUG_INTERRUPTIVENESS) { Slog.v(TAG, "INTERRUPTIVENESS: " @@ -10654,6 +10733,22 @@ public class NotificationManagerService extends SystemService { } @GuardedBy("mNotificationLock") + @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING) + private @NonNull List<NotificationRecord> findAppNotificationByListLocked( + ArrayList<NotificationRecord> list, String pkg, int userId) { + List<NotificationRecord> records = new ArrayList<>(); + final int len = list.size(); + for (int i = 0; i < len; i++) { + NotificationRecord r = list.get(i); + if (notificationMatchesUserId(r, userId, false) + && r.getSbn().getPackageName().equals(pkg)) { + records.add(r); + } + } + return records; + } + + @GuardedBy("mNotificationLock") private @NonNull List<NotificationRecord> findGroupNotificationByListLocked( ArrayList<NotificationRecord> list, String pkg, String groupKey, int userId) { List<NotificationRecord> records = new ArrayList<>(); diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index a4fdb758a740..fcc8d2f74ce9 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -41,6 +41,7 @@ import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_P import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -162,6 +163,7 @@ public class PreferencesHelper implements RankingConfig { private static final String ATT_SENT_VALID_MESSAGE = "sent_valid_msg"; private static final String ATT_USER_DEMOTED_INVALID_MSG_APP = "user_demote_msg_app"; private static final String ATT_SENT_VALID_BUBBLE = "sent_valid_bubble"; + private static final String ATT_PROMOTE_NOTIFS = "promote"; private static final String ATT_CREATION_TIME = "creation_time"; @@ -351,6 +353,10 @@ public class PreferencesHelper implements RankingConfig { r.userDemotedMsgApp = parser.getAttributeBoolean( null, ATT_USER_DEMOTED_INVALID_MSG_APP, false); r.hasSentValidBubble = parser.getAttributeBoolean(null, ATT_SENT_VALID_BUBBLE, false); + if (android.app.Flags.uiRichOngoing()) { + r.canHavePromotedNotifs = + parser.getAttributeBoolean(null, ATT_PROMOTE_NOTIFS, false); + } final int innerDepth = parser.getDepth(); int type; @@ -739,6 +745,11 @@ public class PreferencesHelper implements RankingConfig { out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP, r.userDemotedMsgApp); out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble); + if (android.app.Flags.uiRichOngoing()) { + if (r.canHavePromotedNotifs) { + out.attributeBoolean(null, ATT_PROMOTE_NOTIFS, r.canHavePromotedNotifs); + } + } if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) { out.attributeLong(null, ATT_CREATION_TIME, r.creationTime); @@ -839,6 +850,28 @@ public class PreferencesHelper implements RankingConfig { } } + @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING) + public boolean canBePromoted(String packageName, int uid) { + synchronized (mLock) { + return getOrCreatePackagePreferencesLocked(packageName, uid).canHavePromotedNotifs; + } + } + + @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING) + public boolean setCanBePromoted(String packageName, int uid, boolean promote) { + boolean changed = false; + synchronized (mLock) { + PackagePreferences pkgPrefs = getOrCreatePackagePreferencesLocked(packageName, uid); + if (pkgPrefs.canHavePromotedNotifs != promote) { + pkgPrefs.canHavePromotedNotifs = promote; + changed = true; + } + } + // no need to send a ranking update because we need to update the flag value on all pending + // and posted notifs and NMS will take care of that + return changed; + } + public boolean isInInvalidMsgState(String packageName, int uid) { synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); @@ -2180,6 +2213,10 @@ public class PreferencesHelper implements RankingConfig { pw.print(" fixedImportance="); pw.print(r.fixedImportance); } + if (android.app.Flags.uiRichOngoing() && r.canHavePromotedNotifs) { + pw.print(" promoted="); + pw.print(r.canHavePromotedNotifs); + } pw.println(); for (NotificationChannel channel : r.channels.values()) { pw.print(prefix); @@ -3028,6 +3065,9 @@ public class PreferencesHelper implements RankingConfig { boolean migrateToPm = false; long creationTime; + @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING) + boolean canHavePromotedNotifs = false; + @UserIdInt int userId; Delegate delegate = null; diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 0eb4cbda72bf..626c3ddd49d9 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -1722,7 +1722,7 @@ public class ZenModeHelper { // booleans to determine whether to reset the rules to the default rules boolean allRulesDisabled = true; boolean hasDefaultRules = config.automaticRules.containsAll( - ZenModeConfig.DEFAULT_RULE_IDS); + ZenModeConfig.getDefaultRuleIds()); long time = Flags.modesApi() ? mClock.millis() : System.currentTimeMillis(); if (config.automaticRules != null && config.automaticRules.size() > 0) { @@ -1799,6 +1799,14 @@ public class ZenModeHelper { config.deletedRules.clear(); } + if (Flags.modesUi() && config.automaticRules != null) { + ZenRule obsoleteEventsRule = config.automaticRules.get( + ZenModeConfig.EVENTS_OBSOLETE_RULE_ID); + if (obsoleteEventsRule != null && !obsoleteEventsRule.enabled) { + config.automaticRules.remove(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID); + } + } + if (DEBUG) Log.d(TAG, reason); synchronized (mConfigLock) { setConfigLocked(config, null, @@ -2257,7 +2265,7 @@ public class ZenModeHelper { private static void updateRuleStringsForCurrentLocale(Context context, ZenModeConfig defaultConfig) { for (ZenRule rule : defaultConfig.automaticRules.values()) { - if (ZenModeConfig.EVENTS_DEFAULT_RULE_ID.equals(rule.id)) { + if (ZenModeConfig.EVENTS_OBSOLETE_RULE_ID.equals(rule.id)) { rule.name = context.getResources() .getString(R.string.zen_mode_default_events_name); } else if (ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID.equals(rule.id)) { @@ -2279,7 +2287,7 @@ public class ZenModeHelper { } ZenPolicy defaultPolicy = defaultConfig.getZenPolicy(); for (ZenRule rule : defaultConfig.automaticRules.values()) { - if (ZenModeConfig.DEFAULT_RULE_IDS.contains(rule.id) && rule.zenPolicy == null) { + if (ZenModeConfig.getDefaultRuleIds().contains(rule.id) && rule.zenPolicy == null) { rule.zenPolicy = defaultPolicy.copy(); } } @@ -2483,7 +2491,7 @@ public class ZenModeHelper { List<StatsEvent> events) { // Make the ID safe. String id = rule.id == null ? "" : rule.id; - if (!ZenModeConfig.DEFAULT_RULE_IDS.contains(id)) { + if (!ZenModeConfig.getDefaultRuleIds().contains(id)) { id = ""; } diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index e3d71e4998be..f78c4488cbfb 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -81,7 +81,7 @@ import java.util.function.Consumer; public final class RollbackPackageHealthObserver implements PackageHealthObserver { private static final String TAG = "RollbackPackageHealthObserver"; private static final String NAME = "rollback-observer"; - private static final String ACTION_NAME = RollbackPackageHealthObserver.class.getName(); + private static final String CLASS_NAME = RollbackPackageHealthObserver.class.getName(); private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM; @@ -610,14 +610,16 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } }; + String intentActionName = CLASS_NAME + rollback.getRollbackId(); // Register the BroadcastReceiver mContext.registerReceiver(rollbackReceiver, - new IntentFilter(ACTION_NAME), + new IntentFilter(intentActionName), Context.RECEIVER_NOT_EXPORTED); - Intent intentReceiver = new Intent(ACTION_NAME); + Intent intentReceiver = new Intent(intentActionName); intentReceiver.putExtra("rollbackId", rollback.getRollbackId()); intentReceiver.setPackage(mContext.getPackageName()); + intentReceiver.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext, rollback.getRollbackId(), diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index ccc9b17ff840..12d733fc8c1a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2641,9 +2641,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return true; } // Only do transfer after transaction has done when starting window exist. - if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommit) { - mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT; - return true; + if (mStartingData != null) { + final boolean isWaitingForSyncTransactionCommit = + Flags.removeStartingWindowWaitForMultiTransitions() + ? getSyncTransactionCommitCallbackDepth() > 0 + : mStartingData.mWaitForSyncTransactionCommit; + if (isWaitingForSyncTransactionCommit) { + mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT; + return true; + } } requestCopySplashScreen(); return isTransferringSplashScreen(); @@ -2847,7 +2853,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final boolean animate; final boolean hasImeSurface; if (mStartingData != null) { - if (mStartingData.mWaitForSyncTransactionCommit + final boolean isWaitingForSyncTransactionCommit = + Flags.removeStartingWindowWaitForMultiTransitions() + ? getSyncTransactionCommitCallbackDepth() > 0 + : mStartingData.mWaitForSyncTransactionCommit; + if (isWaitingForSyncTransactionCommit || mSyncState != SYNC_STATE_NONE) { mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY; mStartingData.mPrepareRemoveAnimation = prepareAnimation; diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index f0a4763796e3..57b879277326 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -75,7 +75,8 @@ class InsetsSourceProvider { private final Rect mTmpRect = new Rect(); private final InsetsSourceControl mFakeControl; - private final Consumer<Transaction> mSetLeashPositionConsumer; + private final Point mPosition = new Point(); + private final Consumer<Transaction> mSetControlPositionConsumer; private @Nullable InsetsControlTarget mPendingControlTarget; private @Nullable InsetsControlTarget mFakeControlTarget; @@ -126,13 +127,14 @@ class InsetsSourceProvider { source.getId(), source.getType(), null /* leash */, false /* initialVisible */, new Point(), Insets.NONE); mControllable = (InsetsPolicy.CONTROLLABLE_TYPES & source.getType()) != 0; - mSetLeashPositionConsumer = t -> { - if (mControl != null) { - final SurfaceControl leash = mControl.getLeash(); - if (leash != null) { - final Point position = mControl.getSurfacePosition(); - t.setPosition(leash, position.x, position.y); - } + mSetControlPositionConsumer = t -> { + if (mControl == null || mControlTarget == null) { + return; + } + boolean changed = mControl.setSurfacePosition(mPosition.x, mPosition.y); + final SurfaceControl leash = mControl.getLeash(); + if (changed && leash != null) { + t.setPosition(leash, mPosition.x, mPosition.y); } if (mHasPendingPosition) { mHasPendingPosition = false; @@ -140,9 +142,22 @@ class InsetsSourceProvider { mStateController.notifyControlTargetChanged(mPendingControlTarget, this); } } + changed |= updateInsetsHint(); + if (changed) { + mStateController.notifyControlChanged(mControlTarget, this); + } }; } + private boolean updateInsetsHint() { + final Insets insetsHint = getInsetsHint(); + if (!mControl.getInsetsHint().equals(insetsHint)) { + mControl.setInsetsHint(insetsHint); + return true; + } + return false; + } + InsetsSource getSource() { return mSource; } @@ -363,26 +378,32 @@ class InsetsSourceProvider { } final boolean serverVisibleChanged = mServerVisible != isServerVisible; setServerVisible(isServerVisible); - updateInsetsControlPosition(windowState, serverVisibleChanged); - } - - void updateInsetsControlPosition(WindowState windowState) { - updateInsetsControlPosition(windowState, false); + final boolean positionChanged = updateInsetsControlPosition(windowState); + if (mControl != null && !positionChanged + // The insets hint would be updated if the position is changed. Here updates it for + // the possible change of the bounds or the server visibility. + && (updateInsetsHint() + || serverVisibleChanged + && android.view.inputmethod.Flags.refactorInsetsController())) { + // Only call notifyControlChanged here when the position is not changed. Otherwise, it + // is called or is scheduled to be called during updateInsetsControlPosition. + mStateController.notifyControlChanged(mControlTarget, this); + } } - private void updateInsetsControlPosition(WindowState windowState, - boolean serverVisibleChanged) { + /** + * @return {#code true} if the surface position of the control is changed. + */ + boolean updateInsetsControlPosition(WindowState windowState) { if (mControl == null) { - return; + return false; } - boolean changed = false; final Point position = getWindowFrameSurfacePosition(); - if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) { - changed = true; + if (!mPosition.equals(position)) { + mPosition.set(position.x, position.y); if (windowState != null && windowState.getWindowFrames().didFrameSizeChange() && windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) { - mHasPendingPosition = true; - windowState.applyWithNextDraw(mSetLeashPositionConsumer); + windowState.applyWithNextDraw(mSetControlPositionConsumer); } else { Transaction t = mWindowContainer.getSyncTransaction(); if (windowState != null) { @@ -399,20 +420,11 @@ class InsetsSourceProvider { } } } - mSetLeashPositionConsumer.accept(t); + mSetControlPositionConsumer.accept(t); } + return true; } - final Insets insetsHint = getInsetsHint(); - if (!mControl.getInsetsHint().equals(insetsHint)) { - mControl.setInsetsHint(insetsHint); - changed = true; - } - if (android.view.inputmethod.Flags.refactorInsetsController() && serverVisibleChanged) { - changed = true; - } - if (changed) { - mStateController.notifyControlChanged(mControlTarget, this); - } + return false; } private Point getWindowFrameSurfacePosition() { diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java index 24fb20731c43..896612d3d27a 100644 --- a/services/core/java/com/android/server/wm/StartingData.java +++ b/services/core/java/com/android/server/wm/StartingData.java @@ -68,7 +68,9 @@ public abstract class StartingData { * window. * Note this isn't equal to transition playing, the period should be * Sync finishNow -> Start transaction apply. + * @deprecated TODO(b/362347290): cleanup after fix ramp up */ + @Deprecated boolean mWaitForSyncTransactionCommit; /** diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 0a9cb1c38dab..1c03ba571923 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -4351,4 +4351,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< t.merge(mSyncTransaction); } + int getSyncTransactionCommitCallbackDepth() { + return mSyncTransactionCommitCallbackDepth; + } } diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt index c05c3819ca28..bc64e158e830 100644 --- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt @@ -36,7 +36,6 @@ import androidx.test.platform.app.InstrumentationRegistry import com.android.internal.infra.AndroidFuture import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults import com.google.common.truth.Truth.assertThat -import com.google.common.util.concurrent.MoreExecutors import java.util.concurrent.atomic.AtomicBoolean import org.junit.Test import org.junit.runner.RunWith @@ -46,7 +45,6 @@ import org.junit.runners.JUnit4 class MetadataSyncAdapterTest { private val context = InstrumentationRegistry.getInstrumentation().targetContext private val appSearchManager = context.getSystemService(AppSearchManager::class.java) - private val testExecutor = MoreExecutors.directExecutor() private val packageManager = context.packageManager @Test @@ -138,8 +136,7 @@ class MetadataSyncAdapterTest { PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build() runtimeSearchSession.put(putDocumentsRequest).get() staticSearchSession.put(putDocumentsRequest).get() - val metadataSyncAdapter = - MetadataSyncAdapter(testExecutor, packageManager, appSearchManager) + val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager) val submitSyncRequest = metadataSyncAdapter.trySyncAppFunctionMetadataBlocking( @@ -180,8 +177,7 @@ class MetadataSyncAdapterTest { val putDocumentsRequest: PutDocumentsRequest = PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build() staticSearchSession.put(putDocumentsRequest).get() - val metadataSyncAdapter = - MetadataSyncAdapter(testExecutor, packageManager, appSearchManager) + val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager) val submitSyncRequest = metadataSyncAdapter.trySyncAppFunctionMetadataBlocking( @@ -236,8 +232,7 @@ class MetadataSyncAdapterTest { val putDocumentsRequest: PutDocumentsRequest = PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build() runtimeSearchSession.put(putDocumentsRequest).get() - val metadataSyncAdapter = - MetadataSyncAdapter(testExecutor, packageManager, appSearchManager) + val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager) val submitSyncRequest = metadataSyncAdapter.trySyncAppFunctionMetadataBlocking( 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 b8f9767b5512..130690d80b70 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -38,6 +38,7 @@ import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_NO_DISMISS; import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; +import static android.app.Notification.FLAG_PROMOTED_ONGOING; import static android.app.Notification.FLAG_USER_INITIATED_JOB; import static android.app.Notification.GROUP_ALERT_CHILDREN; import static android.app.Notification.VISIBILITY_PRIVATE; @@ -53,6 +54,7 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MAX; +import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS; @@ -468,6 +470,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationChannel mSilentChannel = new NotificationChannel("low", "low", IMPORTANCE_LOW); + NotificationChannel mMinChannel = new NotificationChannel("min", "min", IMPORTANCE_MIN); + private static final int NOTIFICATION_LOCATION_UNKNOWN = 0; private static final String VALID_CONVO_SHORTCUT_ID = "shortcut"; @@ -558,8 +562,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { - return FlagsParameterization.allCombinationsOf( - FLAG_ALL_NOTIFS_NEED_TTL); + return FlagsParameterization.allCombinationsOf(); } public NotificationManagerServiceTest(FlagsParameterization flags) { @@ -856,15 +859,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mInternalService = mService.getInternalService(); mBinderService.createNotificationChannels(mPkg, new ParceledListSlice( - Arrays.asList(mTestNotificationChannel, mSilentChannel))); + Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel))); mBinderService.createNotificationChannels(PKG_P, new ParceledListSlice( - Arrays.asList(mTestNotificationChannel, mSilentChannel))); + Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel))); mBinderService.createNotificationChannels(PKG_O, new ParceledListSlice( - Arrays.asList(mTestNotificationChannel, mSilentChannel))); + Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel))); assertNotNull(mBinderService.getNotificationChannel( mPkg, mContext.getUserId(), mPkg, TEST_CHANNEL_ID)); assertNotNull(mBinderService.getNotificationChannel( mPkg, mContext.getUserId(), mPkg, mSilentChannel.getId())); + assertNotNull(mBinderService.getNotificationChannel( + mPkg, mContext.getUserId(), mPkg, mMinChannel.getId())); clearInvocations(mRankingHandler); when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); @@ -943,6 +948,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } } + private ShortcutInfo createMockConvoShortcut() { + ShortcutInfo info = mock(ShortcutInfo.class); + when(info.getPackage()).thenReturn(mPkg); + when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID); + when(info.getUserId()).thenReturn(USER_SYSTEM); + when(info.isLongLived()).thenReturn(true); + when(info.isEnabled()).thenReturn(true); + return info; + } + private void simulatePackageSuspendBroadcast(boolean suspend, String pkg, int uid) { // mimics receive broadcast that package is (un)suspended @@ -16540,13 +16555,298 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID); } - private ShortcutInfo createMockConvoShortcut() { - ShortcutInfo info = mock(ShortcutInfo.class); - when(info.getPackage()).thenReturn(mPkg); - when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID); - when(info.getUserId()).thenReturn(USER_SYSTEM); - when(info.isLongLived()).thenReturn(true); - when(info.isEnabled()).thenReturn(true); - return info; + @Test + @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING) + public void testSetCanBePromoted_granted() throws Exception { + mContext.getTestablePermissions().setPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); + // qualifying posted notification + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post + .build(); + + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + // qualifying enqueued notification + Notification n1 = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post + .build(); + StatusBarNotification sbn1 = new StatusBarNotification(mPkg, mPkg, 7, null, mUid, 0, + n1, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mTestNotificationChannel); + + // another package but otherwise would qualify + Notification n2 = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post + .build(); + StatusBarNotification sbn2 = new StatusBarNotification(PKG_O, PKG_O, 7, null, UID_O, 0, + n2, UserHandle.getUserHandleForUid(UID_O), null, 0); + NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mTestNotificationChannel); + + // not-qualifying posted notification + Notification n3 = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + + StatusBarNotification sbn3 = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, + n3, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r3 = new NotificationRecord(mContext, sbn3, mTestNotificationChannel); + + mService.addNotification(r3); + mService.addNotification(r2); + mService.addNotification(r); + mService.addEnqueuedNotification(r1); + + mBinderService.setCanBePromoted(mPkg, mUid, true); + + waitForIdle(); + + ArgumentCaptor<NotificationRecord> captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked( + captor.capture(), any(), anyBoolean()); + + // the posted one + assertThat(mService.hasFlag(captor.getValue().getNotification().flags, + FLAG_PROMOTED_ONGOING)).isTrue(); + // the enqueued one + assertThat(mService.hasFlag(r1.getNotification().flags, FLAG_PROMOTED_ONGOING)).isTrue(); + // the other app + assertThat(mService.hasFlag(r2.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse(); + // same app, not qualifying + assertThat(mService.hasFlag(r3.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse(); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING) + public void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception { + mContext.getTestablePermissions().setPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); + // qualifying posted notification + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post + .build(); + + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addNotification(r); + + mBinderService.setCanBePromoted(mPkg, mUid, true); + waitForIdle(); + mBinderService.setCanBePromoted(mPkg, mUid, true); + waitForIdle(); + + ArgumentCaptor<NotificationRecord> captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked( + captor.capture(), any(), anyBoolean()); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING) + public void testSetCanBePromoted_revoked() throws Exception { + mContext.getTestablePermissions().setPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); + // start from true state + mBinderService.setCanBePromoted(mPkg, mUid, true); + + // qualifying posted notification + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post + .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post + .build(); + + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + // qualifying enqueued notification + Notification n1 = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post + .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post + .build(); + StatusBarNotification sbn1 = new StatusBarNotification(mPkg, mPkg, 7, null, mUid, 0, + n1, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mTestNotificationChannel); + + // doesn't qualify, same package + Notification n2 = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + StatusBarNotification sbn2 = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, + n2, UserHandle.getUserHandleForUid(UID_O), null, 0); + NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mTestNotificationChannel); + + mService.addNotification(r2); + mService.addNotification(r); + mService.addEnqueuedNotification(r1); + + mBinderService.setCanBePromoted(mPkg, mUid, false); + + waitForIdle(); + + ArgumentCaptor<NotificationRecord> captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked( + captor.capture(), any(), anyBoolean()); + + // the posted one + assertThat(mService.hasFlag(captor.getValue().getNotification().flags, + FLAG_PROMOTED_ONGOING)).isFalse(); + // the enqueued one + assertThat(mService.hasFlag(r1.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse(); + // the not qualifying one + assertThat(mService.hasFlag(r2.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse(); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING) + public void testSetCanBePromoted_revoked_onlyNotifiesOnce() throws Exception { + mContext.getTestablePermissions().setPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); + // start from true state + mBinderService.setCanBePromoted(mPkg, mUid, true); + + // qualifying posted notification + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post + .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post + .build(); + + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addNotification(r); + + mBinderService.setCanBePromoted(mPkg, mUid, false); + waitForIdle(); + mBinderService.setCanBePromoted(mPkg, mUid, false); + waitForIdle(); + + ArgumentCaptor<NotificationRecord> captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked( + captor.capture(), any(), anyBoolean()); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING) + public void testPostPromotableNotification() throws Exception { + mBinderService.setCanBePromoted(mPkg, mUid, true); + assertThat(mBinderService.canBePromoted(mPkg, mUid)).isTrue(); + mContext.getTestablePermissions().setPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); + + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .build(); + //assertThat(n.hasPromotableCharacteristics()).isTrue(); + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(), + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + waitForIdle(); + + ArgumentCaptor<NotificationRecord> captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked( + captor.capture(), any(), anyBoolean()); + + assertThat(mService.hasFlag(captor.getValue().getNotification().flags, + FLAG_PROMOTED_ONGOING)).isTrue(); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING) + public void testPostPromotableNotification_noPermission() throws Exception { + mContext.getTestablePermissions().setPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .build(); + + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(), + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + waitForIdle(); + + ArgumentCaptor<NotificationRecord> captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked( + captor.capture(), any(), anyBoolean()); + + assertThat(mService.hasFlag(captor.getValue().getNotification().flags, + FLAG_PROMOTED_ONGOING)).isFalse(); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING) + public void testPostPromotableNotification_unimportantNotification() throws Exception { + mBinderService.setCanBePromoted(mPkg, mUid, true); + mContext.getTestablePermissions().setPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); + Notification n = new Notification.Builder(mContext, mMinChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .build(); + + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(), + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + waitForIdle(); + + ArgumentCaptor<NotificationRecord> captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked( + captor.capture(), any(), anyBoolean()); + + assertThat(mService.hasFlag(captor.getValue().getNotification().flags, + FLAG_PROMOTED_ONGOING)).isFalse(); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 1905ae4aec4b..7d63062784f9 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -518,6 +518,17 @@ public class PreferencesHelperTest extends UiServiceTestCase { doneLatch.await(); } + private static NotificationChannel cloneChannel(NotificationChannel original) { + Parcel parcel = Parcel.obtain(); + try { + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return NotificationChannel.CREATOR.createFromParcel(parcel); + } finally { + parcel.recycle(); + } + } + @Test public void testWriteXml_onlyBackupsTargetUser() throws Exception { // Setup package notifications. @@ -631,6 +642,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { } mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true); + if (android.app.Flags.uiRichOngoing()) { + mHelper.setCanBePromoted(PKG_N_MR1, UID_N_MR1, true); + } ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false, UserHandle.USER_ALL, channel1.getId(), channel2.getId(), @@ -641,6 +655,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { loadStreamXml(baos, false, UserHandle.USER_ALL); assertTrue(mXmlHelper.canShowBadge(PKG_N_MR1, UID_N_MR1)); + if (android.app.Flags.uiRichOngoing()) { + assertThat(mXmlHelper.canBePromoted(PKG_N_MR1, UID_N_MR1)).isTrue(); + } assertEquals(channel1, mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false)); compareChannels(channel2, @@ -6293,14 +6310,21 @@ public class PreferencesHelperTest extends UiServiceTestCase { }, 20, 50); } - private static NotificationChannel cloneChannel(NotificationChannel original) { - Parcel parcel = Parcel.obtain(); - try { - original.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - return NotificationChannel.CREATOR.createFromParcel(parcel); - } finally { - parcel.recycle(); - } + @Test + @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING) + public void testNoAppHasPermissionToPromoteByDefault() { + mHelper.setShowBadge(PKG_P, UID_P, true); + assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse(); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING) + public void testSetCanBePromoted() { + mHelper.setCanBePromoted(PKG_P, UID_P, true); + assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue(); + + mHelper.setCanBePromoted(PKG_P, UID_P, false); + assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse(); + verify(mHandler, never()).requestSort(); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 12069284e462..d4cba8d726fb 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -18,7 +18,7 @@ package com.android.server.notification; import static android.app.AutomaticZenRule.TYPE_BEDTIME; import static android.app.AutomaticZenRule.TYPE_IMMERSIVE; -import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; +import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; import static android.app.AutomaticZenRule.TYPE_UNKNOWN; import static android.app.Flags.FLAG_MODES_API; import static android.app.Flags.FLAG_MODES_UI; @@ -221,7 +221,7 @@ import platform.test.runner.parameterized.Parameters; @TestableLooper.RunWithLooper public class ZenModeHelperTest extends UiServiceTestCase { - private static final String EVENTS_DEFAULT_RULE_ID = ZenModeConfig.EVENTS_DEFAULT_RULE_ID; + private static final String EVENTS_DEFAULT_RULE_ID = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID; private static final String SCHEDULE_DEFAULT_RULE_ID = ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID; private static final String CUSTOM_PKG_NAME = "not.android"; @@ -1216,7 +1216,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // list for tracking which ids we've seen in the pulled atom output List<String> ids = new ArrayList<>(); - ids.addAll(ZenModeConfig.DEFAULT_RULE_IDS); + ids.addAll(ZenModeConfig.getDefaultRuleIds()); ids.add(""); // empty string for root config for (StatsEvent ev : events) { @@ -1793,14 +1793,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { // check default rules ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules; assertTrue(rules.size() != 0); - for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) { + for (String defaultId : ZenModeConfig.getDefaultRuleIds()) { assertTrue(rules.containsKey(defaultId)); } assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy()); } - @Test public void testReadXmlAllDisabledRulesResetDefaultRules() throws Exception { setupZenConfig(); @@ -1830,7 +1829,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // check default rules ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules; assertTrue(rules.size() != 0); - for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) { + for (String defaultId : ZenModeConfig.getDefaultRuleIds()) { assertTrue(rules.containsKey(defaultId)); } assertFalse(rules.containsKey("customRule")); @@ -1839,6 +1838,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + @DisableFlags(FLAG_MODES_UI) // modes_ui has only 1 default rule public void testReadXmlOnlyOneDefaultRuleExists() throws Exception { setupZenConfig(); Policy originalPolicy = mZenModeHelper.getNotificationPolicy(); @@ -1882,11 +1882,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { // check default rules ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules; - assertTrue(rules.size() != 0); - for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) { - assertTrue(rules.containsKey(defaultId)); + assertThat(rules).isNotEmpty(); + for (String defaultId : ZenModeConfig.getDefaultRuleIds()) { + assertThat(rules).containsKey(defaultId); } - assertFalse(rules.containsKey("customRule")); + assertThat(rules).doesNotContainKey("customRule"); assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy()); } @@ -1932,13 +1932,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId( defaultEventRuleInfo); - defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID; + defaultEventRule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID; defaultScheduleRule.zenPolicy = new ZenPolicy.Builder() .allowAlarms(false) .allowMedia(false) .allowRepeatCallers(false) .build(); - automaticRules.put(ZenModeConfig.EVENTS_DEFAULT_RULE_ID, defaultEventRule); + automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, defaultEventRule); mZenModeHelper.mConfig.automaticRules = automaticRules; @@ -1951,18 +1951,19 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL); // check default rules + int expectedNumAutoRules = 1 + ZenModeConfig.getDefaultRuleIds().size(); // custom + default ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules; - assertEquals(3, rules.size()); - for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) { - assertTrue(rules.containsKey(defaultId)); + assertThat(rules).hasSize(expectedNumAutoRules); + for (String defaultId : ZenModeConfig.getDefaultRuleIds()) { + assertThat(rules).containsKey(defaultId); } - assertTrue(rules.containsKey("customRule")); + assertThat(rules).containsKey("customRule"); assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy()); List<StatsEvent> events = new LinkedList<>(); mZenModeHelper.pullRules(events); - assertEquals(4, events.size()); + assertThat(events).hasSize(expectedNumAutoRules + 1); // auto + manual } @Test @@ -2151,8 +2152,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId( defaultEventRuleInfo); - defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID; - automaticRules.put(ZenModeConfig.EVENTS_DEFAULT_RULE_ID, defaultEventRule); + defaultEventRule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID; + automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, defaultEventRule); mZenModeHelper.mConfig.automaticRules = automaticRules; @@ -2167,7 +2168,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // check default rules ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules; assertThat(rules.size()).isGreaterThan(0); - for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) { + for (String defaultId : ZenModeConfig.getDefaultRuleIds()) { assertThat(rules).containsKey(defaultId); ZenRule rule = rules.get(defaultId); assertThat(rule.zenPolicy).isNotNull(); @@ -2371,7 +2372,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Find default rules; check they have non-null policies; check that they match the default // and not whatever has been set up in setupZenConfig. ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules; - for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) { + for (String defaultId : ZenModeConfig.getDefaultRuleIds()) { assertThat(rules).containsKey(defaultId); ZenRule rule = rules.get(defaultId); assertThat(rule.zenPolicy).isNotNull(); @@ -6884,7 +6885,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.onUserSwitched(101); ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get( - ZenModeConfig.EVENTS_DEFAULT_RULE_ID); + ZenModeConfig.EVENTS_OBSOLETE_RULE_ID); assertThat(eventsRule).isNotNull(); assertThat(eventsRule.zenPolicy).isNull(); @@ -6900,7 +6901,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.onUserSwitched(201); ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get( - ZenModeConfig.EVENTS_DEFAULT_RULE_ID); + ZenModeConfig.EVENTS_OBSOLETE_RULE_ID); assertThat(eventsRule).isNotNull(); assertThat(eventsRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy()); @@ -6915,11 +6916,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.onUserSwitched(301); ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get( - ZenModeConfig.EVENTS_DEFAULT_RULE_ID); + ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID); assertThat(eventsRule).isNotNull(); assertThat(eventsRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy()); - assertThat(eventsRule.type).isEqualTo(TYPE_SCHEDULE_CALENDAR); + assertThat(eventsRule.type).isEqualTo(TYPE_SCHEDULE_TIME); assertThat(eventsRule.triggerDescription).isNotEmpty(); } @@ -7008,6 +7009,46 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(zenRule.zenPolicy).isNotSameInstanceAs(mZenModeHelper.getDefaultZenPolicy()); } + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void readXml_withDisabledEventsRule_deletesIt() throws Exception { + ZenRule rule = new ZenRule(); + rule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID; + rule.name = "Events"; + rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; + rule.conditionId = Uri.parse("events"); + + rule.enabled = false; + mZenModeHelper.mConfig.automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, rule); + ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI); + TypedXmlPullParser parser = getParserForByteStream(xmlBytes); + + mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertThat(mZenModeHelper.mConfig.automaticRules).doesNotContainKey( + ZenModeConfig.EVENTS_OBSOLETE_RULE_ID); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void readXml_withEnabledEventsRule_keepsIt() throws Exception { + ZenRule rule = new ZenRule(); + rule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID; + rule.name = "Events"; + rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; + rule.conditionId = Uri.parse("events"); + + rule.enabled = true; + mZenModeHelper.mConfig.automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, rule); + ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI); + TypedXmlPullParser parser = getParserForByteStream(xmlBytes); + + mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertThat(mZenModeHelper.mConfig.automaticRules).containsKey( + ZenModeConfig.EVENTS_OBSOLETE_RULE_ID); + } + private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode, @Nullable ZenPolicy zenPolicy) { ZenRule rule = new ZenRule(); |