summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp5
-rw-r--r--core/api/current.txt1
-rw-r--r--core/java/android/app/INotificationManager.aidl2
-rw-r--r--core/java/android/app/Notification.java58
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java24
-rw-r--r--core/java/android/view/ImeInsetsSourceConsumer.java9
-rw-r--r--core/java/android/view/InsetsAnimationControlCallbacks.java7
-rw-r--r--core/java/android/view/InsetsAnimationControlImpl.java10
-rw-r--r--core/java/android/view/InsetsAnimationControlRunner.java28
-rw-r--r--core/java/android/view/InsetsAnimationThreadControlRunner.java42
-rw-r--r--core/java/android/view/InsetsController.java21
-rw-r--r--core/java/android/view/InsetsResizeAnimationRunner.java5
-rw-r--r--core/java/android/view/InsetsSourceConsumer.java71
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig11
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java18
-rw-r--r--core/java/com/android/internal/protolog/ProtoLog.java8
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogImpl.java30
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java281
-rw-r--r--core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java13
-rw-r--r--core/tests/coretests/src/android/view/InsetsControllerTest.java7
-rw-r--r--core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java13
-rw-r--r--media/java/android/media/flags/editing.aconfig7
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java4
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java53
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java7
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java36
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java12
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java113
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java40
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java16
-rw-r--r--services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java8
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java18
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java78
-rw-r--r--services/core/java/com/android/server/wm/StartingData.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java3
-rw-r--r--services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt11
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java326
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java42
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java91
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();