Merge "Temporary hide notifications when folding/unfolding" into main
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
index 8494326..4a0d57f 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -536,7 +536,7 @@
     private ParcelFileDescriptor createRevocableFd(FileDescriptor fd,
             String callingPackage, int callingUid) throws IOException {
         final RevocableFileDescriptor revocableFd =
-                new RevocableFileDescriptor(mContext, fd);
+                new RevocableFileDescriptor(mContext, fd, BlobStoreUtils.getRevocableFdHandler());
         final Accessor accessor;
         synchronized (mRevocableFds) {
             accessor = new Accessor(callingPackage, callingUid);
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
index 8eef8ce..ede29ec 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -223,7 +223,8 @@
         FileDescriptor fd = null;
         try {
             fd = openWriteInternal(offsetBytes, lengthBytes);
-            final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd);
+            final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd,
+                    BlobStoreUtils.getRevocableFdHandler());
             synchronized (mSessionLock) {
                 if (mState != STATE_OPENED) {
                     IoUtils.closeQuietly(fd);
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java
index 8f94273..a4eae014b 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java
@@ -24,6 +24,8 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.UserHandle;
 import android.text.format.TimeMigrationUtils;
 import android.util.Slog;
@@ -63,4 +65,27 @@
     static String formatTime(long timeMs) {
         return TimeMigrationUtils.formatMillisWithFixedFormat(timeMs);
     }
+
+    private static Handler sRevocableFdHandler;
+    private static final Object sLock = new Object();
+
+    // By default, when using a RevocableFileDescriptor, callbacks will be sent to the process'
+    // main looper. In this case that would be system_server's main looper, which is a heavily
+    // contended thread. It can also cause deadlocks, because the volume daemon 'vold' holds a lock
+    // while making these callbacks to the system_server, while at the same time the system_server
+    // main thread can make a call into vold, which requires that same vold lock. To avoid these
+    // issues, use a separate thread for the RevocableFileDescriptor's requests, so that it can
+    // make progress independently of system_server.
+    static @NonNull Handler getRevocableFdHandler() {
+        synchronized (sLock) {
+            if (sRevocableFdHandler != null) {
+                return sRevocableFdHandler;
+            }
+            final HandlerThread t = new HandlerThread("BlobFuseLooper");
+            t.start();
+            sRevocableFdHandler = new Handler(t.getLooper());
+
+            return sRevocableFdHandler;
+        }
+    }
 }
diff --git a/core/api/current.txt b/core/api/current.txt
index d75aafb..7e14ae1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6952,6 +6952,8 @@
     field public static final String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
     field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
     field public static final String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+    field @FlaggedApi("android.app.modes_api") public static final int AUTOMATIC_RULE_STATUS_ACTIVATED = 4; // 0x4
+    field @FlaggedApi("android.app.modes_api") public static final int AUTOMATIC_RULE_STATUS_DEACTIVATED = 5; // 0x5
     field public static final int AUTOMATIC_RULE_STATUS_DISABLED = 2; // 0x2
     field public static final int AUTOMATIC_RULE_STATUS_ENABLED = 1; // 0x1
     field public static final int AUTOMATIC_RULE_STATUS_REMOVED = 3; // 0x3
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index b8bea9d..b0332c3 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -272,6 +273,7 @@
      *     {@link #AUTOMATIC_RULE_STATUS_UNKNOWN}.
      * </p>
      */
+    // TODO (b/309101513): Add new status types to javadoc
     public static final String EXTRA_AUTOMATIC_ZEN_RULE_STATUS =
             "android.app.extra.AUTOMATIC_ZEN_RULE_STATUS";
 
@@ -286,7 +288,8 @@
     /** @hide */
     @IntDef(prefix = { "AUTOMATIC_RULE_STATUS" }, value = {
             AUTOMATIC_RULE_STATUS_ENABLED, AUTOMATIC_RULE_STATUS_DISABLED,
-            AUTOMATIC_RULE_STATUS_REMOVED, AUTOMATIC_RULE_STATUS_UNKNOWN
+            AUTOMATIC_RULE_STATUS_REMOVED, AUTOMATIC_RULE_STATUS_UNKNOWN,
+            AUTOMATIC_RULE_STATUS_ACTIVATED, AUTOMATIC_RULE_STATUS_DEACTIVATED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AutomaticZenRuleStatus {}
@@ -320,6 +323,27 @@
     public static final int AUTOMATIC_RULE_STATUS_REMOVED = 3;
 
     /**
+     * Constant value for {@link #EXTRA_AUTOMATIC_ZEN_RULE_STATUS} - the given rule has been
+     * activated by the user or cross device sync. Sent from
+     * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}. If the rule owner has a mode that includes
+     * a DND component, the rule owner should activate any extra behavior that's part of that mode
+     * in response to this broadcast.
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static final int AUTOMATIC_RULE_STATUS_ACTIVATED = 4;
+
+    /**
+     * Constant value for {@link #EXTRA_AUTOMATIC_ZEN_RULE_STATUS} - the given rule has been
+     * deactivated ("snoozed") by the user. The rule will not return to an activated state until
+     * the app calls {@link #setAutomaticZenRuleState(String, Condition)} with
+     * {@link Condition#STATE_FALSE} (either immediately or when the trigger criteria is no
+     * longer met) and then {@link Condition#STATE_TRUE} when the trigger criteria is freshly met,
+     * or when the user re-activates it.
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static final int AUTOMATIC_RULE_STATUS_DEACTIVATED = 5;
+
+    /**
      * Intent that is broadcast when the state of {@link #getEffectsSuppressor()} changes.
      *
      * <p>This broadcast is only sent to registered receivers and (starting from
diff --git a/core/java/android/os/RevocableFileDescriptor.java b/core/java/android/os/RevocableFileDescriptor.java
index ac2cd60..7093c49 100644
--- a/core/java/android/os/RevocableFileDescriptor.java
+++ b/core/java/android/os/RevocableFileDescriptor.java
@@ -73,11 +73,26 @@
         init(context, fd);
     }
 
+    public RevocableFileDescriptor(Context context, FileDescriptor fd, Handler handler)
+            throws IOException {
+        init(context, fd, handler);
+    }
+
     /** {@hide} */
     public void init(Context context, FileDescriptor fd) throws IOException {
+        init(context, fd, null);
+    }
+
+    /** {@hide} */
+    public void init(Context context, FileDescriptor fd, Handler handler) throws IOException {
         mInner = fd;
-        mOuter = context.getSystemService(StorageManager.class)
-                .openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_WRITE, mCallback);
+        StorageManager sm = context.getSystemService(StorageManager.class);
+        if (handler != null) {
+            mOuter = sm.openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_WRITE, mCallback,
+                    handler);
+        } else {
+            mOuter = sm.openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_WRITE, mCallback);
+        }
     }
 
     /**
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 576ebc1..65e1605 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -74,6 +74,14 @@
             controller = std::shared_ptr<PointerController>(
                     new MousePointerController(policy, looper, spriteController, enabled));
             break;
+        case ControllerType::TOUCH:
+            controller = std::shared_ptr<PointerController>(
+                    new TouchPointerController(policy, looper, spriteController, enabled));
+            break;
+        case ControllerType::STYLUS:
+            controller = std::shared_ptr<PointerController>(
+                    new StylusPointerController(policy, looper, spriteController, enabled));
+            break;
         case ControllerType::LEGACY:
         default:
             controller = std::shared_ptr<PointerController>(
@@ -167,8 +175,7 @@
 
 FloatPoint PointerController::getPosition() const {
     if (!mEnabled) {
-        return FloatPoint{AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                          AMOTION_EVENT_INVALID_CURSOR_POSITION};
+        return FloatPoint{0, 0};
     }
 
     const int32_t displayId = mCursorController.getDisplayId();
@@ -397,4 +404,34 @@
     PointerController::setPresentation(Presentation::POINTER);
 }
 
+MousePointerController::~MousePointerController() {
+    MousePointerController::fade(Transition::IMMEDIATE);
+}
+
+// --- TouchPointerController ---
+
+TouchPointerController::TouchPointerController(const sp<PointerControllerPolicyInterface>& policy,
+                                               const sp<Looper>& looper,
+                                               SpriteController& spriteController, bool enabled)
+      : PointerController(policy, looper, spriteController, enabled) {
+    PointerController::setPresentation(Presentation::SPOT);
+}
+
+TouchPointerController::~TouchPointerController() {
+    TouchPointerController::clearSpots();
+}
+
+// --- StylusPointerController ---
+
+StylusPointerController::StylusPointerController(const sp<PointerControllerPolicyInterface>& policy,
+                                                 const sp<Looper>& looper,
+                                                 SpriteController& spriteController, bool enabled)
+      : PointerController(policy, looper, spriteController, enabled) {
+    PointerController::setPresentation(Presentation::STYLUS_HOVER);
+}
+
+StylusPointerController::~StylusPointerController() {
+    StylusPointerController::fade(Transition::IMMEDIATE);
+}
+
 } // namespace android
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 08e19a0..fa07c39 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -68,7 +68,7 @@
 
     void updatePointerIcon(PointerIconStyle iconId);
     void setCustomPointerIcon(const SpriteIcon& icon);
-    void setInactivityTimeout(InactivityTimeout inactivityTimeout);
+    virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
     void doInactivityTimeout();
     void reloadPointerResources();
     void onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports);
@@ -143,6 +143,74 @@
                            const sp<Looper>& looper, SpriteController& spriteController,
                            bool enabled);
 
+    ~MousePointerController() override;
+
+    void setPresentation(Presentation) override {
+        LOG_ALWAYS_FATAL("Should not be called");
+    }
+    void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override {
+        LOG_ALWAYS_FATAL("Should not be called");
+    }
+    void clearSpots() override {
+        LOG_ALWAYS_FATAL("Should not be called");
+    }
+};
+
+class TouchPointerController : public PointerController {
+public:
+    /** A version of PointerController that controls touch spots. */
+    TouchPointerController(const sp<PointerControllerPolicyInterface>& policy,
+                           const sp<Looper>& looper, SpriteController& spriteController,
+                           bool enabled);
+
+    ~TouchPointerController() override;
+
+    std::optional<FloatRect> getBounds() const override {
+        LOG_ALWAYS_FATAL("Should not be called");
+    }
+    void move(float, float) override {
+        LOG_ALWAYS_FATAL("Should not be called");
+    }
+    void setPosition(float, float) override {
+        LOG_ALWAYS_FATAL("Should not be called");
+    }
+    FloatPoint getPosition() const override {
+        LOG_ALWAYS_FATAL("Should not be called");
+    }
+    int32_t getDisplayId() const override {
+        LOG_ALWAYS_FATAL("Should not be called");
+    }
+    void fade(Transition) override {
+        LOG_ALWAYS_FATAL("Should not be called");
+    }
+    void unfade(Transition) override {
+        LOG_ALWAYS_FATAL("Should not be called");
+    }
+    void setDisplayViewport(const DisplayViewport&) override {
+        LOG_ALWAYS_FATAL("Should not be called");
+    }
+    void setPresentation(Presentation) override {
+        LOG_ALWAYS_FATAL("Should not be called");
+    }
+    void updatePointerIcon(PointerIconStyle) {
+        LOG_ALWAYS_FATAL("Should not be called");
+    }
+    void setCustomPointerIcon(const SpriteIcon&) {
+        LOG_ALWAYS_FATAL("Should not be called");
+    }
+    // fade() should not be called by inactivity timeout. Do nothing.
+    void setInactivityTimeout(InactivityTimeout) override {}
+};
+
+class StylusPointerController : public PointerController {
+public:
+    /** A version of PointerController that controls one stylus pointer. */
+    StylusPointerController(const sp<PointerControllerPolicyInterface>& policy,
+                            const sp<Looper>& looper, SpriteController& spriteController,
+                            bool enabled);
+
+    ~StylusPointerController() override;
+
     void setPresentation(Presentation) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 8230a82..bde0c0c 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -759,13 +759,7 @@
             return;
         }
 
-        synchronized (mLock) {
-            mRoutes.clear();
-            for (MediaRoute2Info route : currentRoutes) {
-                mRoutes.put(route.getId(), route);
-            }
-            updateFilteredRoutesLocked();
-        }
+        updateRoutesOnHandler(currentRoutes);
 
         RoutingSessionInfo oldInfo = mSystemController.getRoutingSessionInfo();
         mSystemController.setRoutingSessionInfo(currentSystemSessionInfo);
@@ -824,10 +818,10 @@
         }
     }
 
-    void updateRoutesOnHandler(List<MediaRoute2Info> routes) {
+    void updateRoutesOnHandler(List<MediaRoute2Info> newRoutes) {
         synchronized (mLock) {
             mRoutes.clear();
-            for (MediaRoute2Info route : routes) {
+            for (MediaRoute2Info route : newRoutes) {
                 mRoutes.put(route.getId(), route);
             }
             updateFilteredRoutesLocked();
@@ -2703,16 +2697,6 @@
             notifyRouteListingPreferenceUpdated(routeListingPreference);
         }
 
-        private void onRoutesUpdatedOnHandler(@NonNull List<MediaRoute2Info> routes) {
-            synchronized (mLock) {
-                mRoutes.clear();
-                for (MediaRoute2Info route : routes) {
-                    mRoutes.put(route.getId(), route);
-                }
-                updateFilteredRoutesLocked();
-            }
-        }
-
         private void onRequestFailedOnHandler(int requestId, int reason) {
             MediaRouter2Manager.TransferRequest matchingRequest = null;
             for (MediaRouter2Manager.TransferRequest request : mTransferRequests) {
@@ -2786,9 +2770,7 @@
             public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
                 mHandler.sendMessage(
                         obtainMessage(
-                                ProxyMediaRouter2Impl::onRoutesUpdatedOnHandler,
-                                ProxyMediaRouter2Impl.this,
-                                routes));
+                                MediaRouter2::updateRoutesOnHandler, MediaRouter2.this, routes));
             }
 
             @Override
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
index 46a033d..e3f8fbb 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
@@ -47,12 +47,14 @@
             android:textAppearance="?android:attr/textAppearanceListItem"
             style="@style/MainSwitchText.Settingslib" />
 
-        <include
+        <Switch
             android:id="@android:id/switch_widget"
-            layout="@layout/preference_widget_switch_compat"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical" />
+            android:layout_height="48dp"
+            android:layout_gravity="center_vertical"
+            android:focusable="false"
+            android:clickable="false"
+            android:theme="@style/Switch.SettingsLib"/>
     </LinearLayout>
 
 </LinearLayout>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
index 6d1e76c..255b2c9 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
@@ -49,12 +49,14 @@
             android:lineBreakWordStyle="phrase"
             style="@style/MainSwitchText.Settingslib" />
 
-        <include
+        <Switch
             android:id="@android:id/switch_widget"
-            layout="@layout/preference_widget_switch_compat"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical" />
+            android:layout_height="48dp"
+            android:layout_gravity="center_vertical"
+            android:focusable="false"
+            android:clickable="false"
+            android:theme="@style/Switch.SettingsLib"/>
     </LinearLayout>
 
 </LinearLayout>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
index 5be0093..bf34db9 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
@@ -38,11 +38,13 @@
         android:layout_marginStart="@dimen/settingslib_switchbar_subsettings_margin_start"
         android:textAlignment="viewStart"/>
 
-    <include
+    <Switch
         android:id="@android:id/switch_widget"
-        layout="@layout/preference_widget_switch_compat"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical" />
+        android:layout_gravity="center_vertical"
+        android:focusable="false"
+        android:clickable="false"
+        android:theme="@style/SwitchBar.Switch.Settingslib"/>
 </LinearLayout>
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
index 5447f21..1c92696 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
@@ -57,15 +57,18 @@
 
 private fun UserManager.getUserGroups(): List<UserGroup> {
     val userGroupList = mutableListOf<UserGroup>()
-    val profileToShowInSettingsList = getProfiles(UserHandle.myUserId())
-        .map { userInfo -> userInfo to getUserProperties(userInfo.userHandle).showInSettings }
+    val profileToShowInSettings = getProfiles(UserHandle.myUserId())
+        .map { userInfo -> userInfo to getUserProperties(userInfo.userHandle) }
 
-    profileToShowInSettingsList.filter { it.second == UserProperties.SHOW_IN_SETTINGS_WITH_PARENT }
+    profileToShowInSettings
+        .filter { it.second.showInSettings == UserProperties.SHOW_IN_SETTINGS_WITH_PARENT }
         .takeIf { it.isNotEmpty() }
         ?.map { it.first }
         ?.let { userInfos -> userGroupList += UserGroup(userInfos) }
 
-    profileToShowInSettingsList.filter { it.second == UserProperties.SHOW_IN_LAUNCHER_SEPARATE }
+    profileToShowInSettings
+        .filter { it.second.showInSettings == UserProperties.SHOW_IN_SETTINGS_SEPARATE &&
+                    (!it.second.hideInSettingsInQuietMode || !it.first.isQuietModeEnabled) }
         .forEach { userGroupList += UserGroup(userInfos = listOf(it.first)) }
 
     return userGroupList
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 03d1cda..e77964c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -243,6 +243,32 @@
     }
 
     /**
+     * Checks whether add user is disabled on the device
+     *
+     * @param context {@link Context} for the calling user.
+     *
+     *
+     * @param userId User to check enforced admin status for.
+     *
+     * @return EnforcedAdmin Object containing the enforced admin component and admin user details,
+     * or {@code null} If adding user is not disabled.
+     */
+    public static EnforcedAdmin checkIfAddUserDisallowed(Context context, int userId) {
+        final UserManager um = UserManager.get(context);
+        if (!um.hasUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.of(userId))) {
+            // Restriction is not enforced.
+            return null;
+        }
+        EnforcedAdmin enforcedAdmin = checkIfRestrictionEnforced(context,
+                UserManager.DISALLOW_ADD_USER, userId);
+        if (enforcedAdmin != null) {
+            return enforcedAdmin;
+        }
+        return EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(
+                UserManager.DISALLOW_ADD_USER);
+    }
+
+    /**
      * Check if an application is suspended.
      *
      * @return EnforcedAdmin Object containing the enforced admin component and admin user details,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index eb10afc..aae61bd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -33,7 +33,9 @@
 import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.isSpecified
+import androidx.compose.ui.geometry.isUnspecified
 import androidx.compose.ui.geometry.lerp
+import androidx.compose.ui.graphics.drawscope.scale
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.IntermediateMeasureScope
 import androidx.compose.ui.layout.Measurable
@@ -85,6 +87,9 @@
         /** The size of this element. */
         var size = SizeUnspecified
 
+        /** The draw scale of this element. */
+        var drawScale = Scale.Default
+
         /** The alpha of this element. */
         var alpha = AlphaUnspecified
     }
@@ -110,6 +115,13 @@
     }
 }
 
+data class Scale(val scaleX: Float, val scaleY: Float, val pivot: Offset = Offset.Unspecified) {
+
+    companion object {
+        val Default = Scale(1f, 1f, Offset.Unspecified)
+    }
+}
+
 /** The implementation of [SceneScope.element]. */
 @OptIn(ExperimentalComposeUiApi::class)
 internal fun Modifier.element(
@@ -160,9 +172,24 @@
         }
     }
 
+    val drawScale by
+        remember(layoutImpl, element, scene, sceneValues) {
+            derivedStateOf { getDrawScale(layoutImpl, element, scene, sceneValues) }
+        }
+
     drawWithContent {
             if (shouldDrawElement(layoutImpl, scene, element)) {
-                drawContent()
+                if (drawScale == Scale.Default) {
+                    this@drawWithContent.drawContent()
+                } else {
+                    scale(
+                        drawScale.scaleX,
+                        drawScale.scaleY,
+                        if (drawScale.pivot.isUnspecified) center else drawScale.pivot
+                    ) {
+                        this@drawWithContent.drawContent()
+                    }
+                }
             }
         }
         .modifierTransformations(layoutImpl, scene, element, sceneValues)
@@ -377,6 +404,28 @@
     return placeable
 }
 
+private fun getDrawScale(
+    layoutImpl: SceneTransitionLayoutImpl,
+    element: Element,
+    scene: Scene,
+    sceneValues: Element.TargetValues
+): Scale {
+    return computeValue(
+        layoutImpl,
+        scene,
+        element,
+        sceneValue = { Scale.Default },
+        transformation = { it.drawScale },
+        idleValue = Scale.Default,
+        currentValue = { Scale.Default },
+        lastValue = {
+            sceneValues.lastValues.drawScale.takeIf { it != Scale.Default }
+                ?: element.lastSharedValues.drawScale
+        },
+        ::lerp,
+    )
+}
+
 @OptIn(ExperimentalComposeUiApi::class)
 private fun IntermediateMeasureScope.place(
     layoutImpl: SceneTransitionLayoutImpl,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index b163a2a..72a2d61 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -25,6 +25,7 @@
 import androidx.compose.ui.util.fastMap
 import com.android.compose.animation.scene.transformation.AnchoredSize
 import com.android.compose.animation.scene.transformation.AnchoredTranslate
+import com.android.compose.animation.scene.transformation.DrawScale
 import com.android.compose.animation.scene.transformation.EdgeTranslate
 import com.android.compose.animation.scene.transformation.Fade
 import com.android.compose.animation.scene.transformation.ModifierTransformation
@@ -126,6 +127,7 @@
         val modifier = mutableListOf<ModifierTransformation>()
         var offset: PropertyTransformation<Offset>? = null
         var size: PropertyTransformation<IntSize>? = null
+        var drawScale: PropertyTransformation<Scale>? = null
         var alpha: PropertyTransformation<Float>? = null
 
         fun <T> onPropertyTransformation(
@@ -144,6 +146,10 @@
                     throwIfNotNull(size, element, name = "size")
                     size = root as PropertyTransformation<IntSize>
                 }
+                is DrawScale -> {
+                    throwIfNotNull(drawScale, element, name = "drawScale")
+                    drawScale = root as PropertyTransformation<Scale>
+                }
                 is Fade -> {
                     throwIfNotNull(alpha, element, name = "alpha")
                     alpha = root as PropertyTransformation<Float>
@@ -167,7 +173,7 @@
             }
         }
 
-        return ElementTransformations(shared, modifier, offset, size, alpha)
+        return ElementTransformations(shared, modifier, offset, size, drawScale, alpha)
     }
 
     private fun throwIfNotNull(
@@ -187,5 +193,6 @@
     val modifier: List<ModifierTransformation>,
     val offset: PropertyTransformation<Offset>?,
     val size: PropertyTransformation<IntSize>?,
+    val drawScale: PropertyTransformation<Scale>?,
     val alpha: PropertyTransformation<Float>?,
 )
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 7b7ddfa..ca66dff5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -17,6 +17,7 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.Dp
@@ -224,12 +225,22 @@
     /**
      * Scale the [width] and [height] of the element(s) matching [matcher]. Note that this scaling
      * is done during layout, so it will potentially impact the size and position of other elements.
-     *
-     * TODO(b/290184746): Also provide a scaleDrawing() to scale an element at drawing time.
      */
     fun scaleSize(matcher: ElementMatcher, width: Float = 1f, height: Float = 1f)
 
     /**
+     * Scale the drawing with [scaleX] and [scaleY] of the element(s) matching [matcher]. Note this
+     * will only scale the draw inside of an element, therefore it won't impact layout of elements
+     * around it.
+     */
+    fun scaleDraw(
+        matcher: ElementMatcher,
+        scaleX: Float = 1f,
+        scaleY: Float = 1f,
+        pivot: Offset = Offset.Unspecified
+    )
+
+    /**
      * Scale the element(s) matching [matcher] so that it grows/shrinks to the same size as [anchor]
      * .
      *
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index d2bfd91..d490989 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -21,10 +21,12 @@
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.animation.core.spring
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.Dp
 import com.android.compose.animation.scene.transformation.AnchoredSize
 import com.android.compose.animation.scene.transformation.AnchoredTranslate
+import com.android.compose.animation.scene.transformation.DrawScale
 import com.android.compose.animation.scene.transformation.EdgeTranslate
 import com.android.compose.animation.scene.transformation.Fade
 import com.android.compose.animation.scene.transformation.PropertyTransformation
@@ -178,6 +180,10 @@
         transformation(ScaleSize(matcher, width, height))
     }
 
+    override fun scaleDraw(matcher: ElementMatcher, scaleX: Float, scaleY: Float, pivot: Offset) {
+        transformation(DrawScale(matcher, scaleX, scaleY, pivot))
+    }
+
     override fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey) {
         transformation(AnchoredSize(matcher, anchor))
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
new file mode 100644
index 0000000..d1cf8ee
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.geometry.Offset
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scale
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+
+/**
+ * Scales the draw size of an element. Note this will only scale the draw inside of an element,
+ * therefore it won't impact layout of elements around it.
+ */
+internal class DrawScale(
+    override val matcher: ElementMatcher,
+    private val scaleX: Float,
+    private val scaleY: Float,
+    private val pivot: Offset = Offset.Unspecified,
+) : PropertyTransformation<Scale> {
+
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.TargetValues,
+        transition: TransitionState.Transition,
+        value: Scale,
+    ): Scale {
+        return Scale(scaleX, scaleY, pivot)
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
index 62d67f0..984086b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
@@ -19,9 +19,11 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.geometry.toRect
 import androidx.compose.ui.graphics.BlendMode
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Outline
 import androidx.compose.ui.graphics.Paint
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
@@ -30,6 +32,7 @@
 import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
 import androidx.compose.ui.graphics.drawscope.translate
 import androidx.compose.ui.graphics.withSaveLayer
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.toSize
 import com.android.compose.animation.scene.Element
 import com.android.compose.animation.scene.ElementKey
@@ -43,6 +46,11 @@
     private val bounds: ElementKey,
     private val shape: Shape,
 ) : ModifierTransformation {
+
+    private var lastSize: Size = Size.Unspecified
+    private var lastLayoutDirection: LayoutDirection = LayoutDirection.Ltr
+    private var lastOutline: Outline? = null
+
     override fun Modifier.transform(
         layoutImpl: SceneTransitionLayoutImpl,
         scene: Scene,
@@ -59,7 +67,6 @@
                 drawContent()
                 return@drawWithContent
             }
-
             drawIntoCanvas { canvas ->
                 canvas.withSaveLayer(size.toRect(), Paint()) {
                     drawContent()
@@ -78,13 +85,19 @@
             return
         }
 
-        // TODO(b/290184746): Cache outline if the size of bounds does not change.
+        val outline =
+            if (boundsSize == lastSize && layoutDirection == lastLayoutDirection) {
+                lastOutline!!
+            } else {
+                val newOutline = shape.createOutline(boundsSize, layoutDirection, this)
+                lastSize = boundsSize
+                lastLayoutDirection = layoutDirection
+                lastOutline = newOutline
+                newOutline
+            }
+
         drawOutline(
-            shape.createOutline(
-                boundsSize,
-                layoutDirection,
-                this,
-            ),
+            outline,
             Color.Black,
             blendMode = BlendMode.DstOut,
         )
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
index eb1a634..13747b7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
@@ -17,7 +17,10 @@
 
 package com.android.compose.ui.util
 
+import androidx.compose.ui.geometry.isSpecified
+import androidx.compose.ui.geometry.lerp
 import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.Scale
 import kotlin.math.roundToInt
 import kotlin.math.roundToLong
 
@@ -43,3 +46,19 @@
         lerp(start.height, stop.height, fraction)
     )
 }
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: Scale, stop: Scale, fraction: Float): Scale {
+    val pivot =
+        when {
+            start.pivot.isSpecified && stop.pivot.isSpecified ->
+                lerp(start.pivot, stop.pivot, fraction)
+            start.pivot.isSpecified -> start.pivot
+            else -> stop.pivot
+        }
+    return Scale(
+        lerp(start.scaleX, stop.scaleX, fraction),
+        lerp(start.scaleY, stop.scaleY, fraction),
+        pivot
+    )
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/MathHelpersTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/MathHelpersTest.kt
new file mode 100644
index 0000000..3ec73c7
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/MathHelpersTest.kt
@@ -0,0 +1,56 @@
+package com.android.compose.ui.util
+
+import androidx.compose.ui.geometry.Offset
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.Scale
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MathHelpersTest {
+
+    @Test
+    fun lerpScaleWithPivotUnspecified() {
+        val scale1 = Scale(1f, 1f)
+        val scale2 = Scale(5f, 3f)
+        val expectedScale = Scale(3f, 2f)
+
+        val actualScale = lerp(scale1, scale2, 0.5f)
+
+        assertThat(actualScale).isEqualTo(expectedScale)
+    }
+
+    @Test
+    fun lerpScaleWithFirstPivotSpecified() {
+        val scale1 = Scale(1f, 1f, Offset(1f, 1f))
+        val scale2 = Scale(5f, 3f)
+        val expectedScale = Scale(3f, 2f, Offset(1f, 1f))
+
+        val actualScale = lerp(scale1, scale2, 0.5f)
+
+        assertThat(actualScale).isEqualTo(expectedScale)
+    }
+
+    @Test
+    fun lerpScaleWithSecondPivotSpecified() {
+        val scale1 = Scale(1f, 1f)
+        val scale2 = Scale(5f, 3f, Offset(1f, 1f))
+        val expectedScale = Scale(3f, 2f, Offset(1f, 1f))
+
+        val actualScale = lerp(scale1, scale2, 0.5f)
+
+        assertThat(actualScale).isEqualTo(expectedScale)
+    }
+
+    @Test
+    fun lerpScaleWithBothPivotsSpecified() {
+        val scale1 = Scale(1f, 1f, Offset(1f, 1f))
+        val scale2 = Scale(5f, 3f, Offset(3f, 5f))
+        val expectedScale = Scale(3f, 2f, Offset(2f, 3f))
+
+        val actualScale = lerp(scale1, scale2, 0.5f)
+
+        assertThat(actualScale).isEqualTo(expectedScale)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index c924df6..3492365 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -45,7 +45,7 @@
 
     /** See [registerCriticalDumpable]. */
     fun registerCriticalDumpable(module: Dumpable) {
-        registerCriticalDumpable(module::class.java.simpleName, module)
+        registerCriticalDumpable(module::class.java.canonicalName, module)
     }
 
     /**
@@ -62,7 +62,7 @@
 
     /** See [registerNormalDumpable]. */
     fun registerNormalDumpable(module: Dumpable) {
-        registerNormalDumpable(module::class.java.simpleName, module)
+        registerNormalDumpable(module::class.java.canonicalName, module)
     }
 
     /**
@@ -105,12 +105,12 @@
     }
 
     /**
-     * Same as the above override, but automatically uses the simple class name as the dumpable
+     * Same as the above override, but automatically uses the canonical class name as the dumpable
      * name.
      */
     @Synchronized
     fun registerDumpable(module: Dumpable) {
-        registerDumpable(module::class.java.simpleName, module)
+        registerDumpable(module::class.java.canonicalName, module)
     }
 
     /** Unregisters a previously-registered dumpable. */
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 9802adf..762c1a1 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -16,14 +16,19 @@
 
 package com.android.server.notification;
 
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED;
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED;
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN;
 import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
 import static android.service.notification.NotificationServiceProto.ROOT_CONFIG;
 
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.AutomaticZenRule;
 import android.app.Flags;
@@ -31,6 +36,9 @@
 import android.app.NotificationManager;
 import android.app.NotificationManager.Policy;
 import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -51,6 +59,7 @@
 import android.media.VolumePolicy;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -109,6 +118,13 @@
     private static final int RULE_INSTANCE_GRACE_PERIOD = 1000 * 60 * 60 * 72;
     static final int RULE_LIMIT_PER_PACKAGE = 100;
 
+    /**
+     * Send new activation AutomaticZenRule statuses to apps with a min target SDK version
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    static final long SEND_ACTIVATION_AZR_STATUSES = 308673617L;
+
     // pkg|userId => uid
     @VisibleForTesting protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>();
 
@@ -229,7 +245,8 @@
             // was read in via XML, but will initialize zen mode if nothing was read in and the
             // config remains the default.
             updateConfigAndZenModeLocked(mConfig, "init", true /*setRingerMode*/,
-                    Process.SYSTEM_UID /* callingUid */, true /* is system */);
+                    Process.SYSTEM_UID /* callingUid */, true /* is system */,
+                    false /* no broadcasts*/);
         }
     }
 
@@ -407,10 +424,13 @@
                             "Cannot update rules not owned by your condition provider");
                 }
             }
-            if (rule.enabled != automaticZenRule.isEnabled()) {
-                dispatchOnAutomaticRuleStatusChanged(mConfig.user, rule.getPkg(), ruleId,
-                        automaticZenRule.isEnabled()
-                                ? AUTOMATIC_RULE_STATUS_ENABLED : AUTOMATIC_RULE_STATUS_DISABLED);
+            if (!Flags.modesApi()) {
+                if (rule.enabled != automaticZenRule.isEnabled()) {
+                    dispatchOnAutomaticRuleStatusChanged(mConfig.user, rule.getPkg(), ruleId,
+                            automaticZenRule.isEnabled()
+                                    ? AUTOMATIC_RULE_STATUS_ENABLED
+                                    : AUTOMATIC_RULE_STATUS_DISABLED);
+                }
             }
 
             populateZenRule(rule.pkg, automaticZenRule, rule, false);
@@ -651,6 +671,9 @@
 
     private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
             boolean isNew) {
+        if (rule.enabled != automaticZenRule.isEnabled()) {
+            rule.snoozing = false;
+        }
         rule.name = automaticZenRule.getName();
         rule.condition = null;
         rule.conditionId = automaticZenRule.getConditionId();
@@ -668,9 +691,6 @@
             rule.pkg = pkg;
         }
 
-        if (rule.enabled != automaticZenRule.isEnabled()) {
-            rule.snoozing = false;
-        }
         if (Flags.modesApi()) {
             rule.allowManualInvocation = automaticZenRule.isManualInvocationAllowed();
             rule.iconResId = automaticZenRule.getIconResId();
@@ -706,6 +726,27 @@
         return azr;
     }
 
+    @SuppressLint("MissingPermission")
+    void scheduleActivationBroadcast(String pkg, @UserIdInt int userId, String ruleId,
+            boolean activated) {
+        if (CompatChanges.isChangeEnabled(
+                SEND_ACTIVATION_AZR_STATUSES, pkg, UserHandle.of(userId))) {
+            dispatchOnAutomaticRuleStatusChanged(userId, pkg, ruleId, activated
+                    ? AUTOMATIC_RULE_STATUS_ACTIVATED
+                    : AUTOMATIC_RULE_STATUS_DEACTIVATED);
+        } else {
+            dispatchOnAutomaticRuleStatusChanged(
+                    userId, pkg, ruleId, AUTOMATIC_RULE_STATUS_UNKNOWN);
+        }
+    }
+
+    void scheduleEnabledBroadcast(String pkg, @UserIdInt int userId, String ruleId,
+            boolean enabled) {
+        dispatchOnAutomaticRuleStatusChanged(userId, pkg, ruleId, enabled
+                ? AUTOMATIC_RULE_STATUS_ENABLED
+                : AUTOMATIC_RULE_STATUS_DISABLED);
+    }
+
     public void setManualZenMode(int zenMode, Uri conditionId, String caller, String reason,
             int callingUid, boolean fromSystemOrSystemUi) {
         setManualZenMode(zenMode, conditionId, reason, caller, true /*setRingerMode*/, callingUid,
@@ -1002,7 +1043,7 @@
                 dispatchOnPolicyChanged();
             }
             updateConfigAndZenModeLocked(config, reason, setRingerMode, callingUid,
-                    fromSystemOrSystemUi);
+                    fromSystemOrSystemUi, true);
             mConditions.evaluateConfig(config, triggeringComponent, true /*processSubscriptions*/);
             return true;
         } catch (SecurityException e) {
@@ -1019,13 +1060,31 @@
      */
     @GuardedBy("mConfigLock")
     private void updateConfigAndZenModeLocked(ZenModeConfig config, String reason,
-            boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) {
+            boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi,
+            boolean sendBroadcasts) {
         final boolean logZenModeEvents = mFlagResolver.isEnabled(
                 SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS);
         // Store (a copy of) all config and zen mode info prior to any changes taking effect
         ZenModeEventLogger.ZenModeInfo prevInfo = new ZenModeEventLogger.ZenModeInfo(
                 mZenMode, mConfig, mConsolidatedPolicy);
         if (!config.equals(mConfig)) {
+            // schedule broadcasts
+            if (Flags.modesApi() && sendBroadcasts) {
+                for (ZenRule rule : config.automaticRules.values()) {
+                    ZenRule original = mConfig.automaticRules.get(rule.id);
+                    if (original != null) {
+                        if (original.enabled != rule.enabled) {
+                            scheduleEnabledBroadcast(
+                                    rule.getPkg(), config.user, rule.id, rule.enabled);
+                        }
+                        if (original.isAutomaticActive() != rule.isAutomaticActive()) {
+                            scheduleActivationBroadcast(
+                                    rule.getPkg(), config.user, rule.id, rule.isAutomaticActive());
+                        }
+                    }
+                }
+            }
+
             mConfig = config;
             dispatchOnConfigChanged();
             updateConsolidatedPolicy(reason);
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index a7d7730..2182093 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1293,6 +1293,11 @@
 }
 
 void NativeInputManager::setShowTouches(bool enabled) {
+    if (ENABLE_POINTER_CHOREOGRAPHER) {
+        mInputManager->getChoreographer().setShowTouchesEnabled(enabled);
+        return;
+    }
+
     { // acquire lock
         std::scoped_lock _l(mLock);
 
@@ -1744,6 +1749,11 @@
 }
 
 void NativeInputManager::setStylusPointerIconEnabled(bool enabled) {
+    if (ENABLE_POINTER_CHOREOGRAPHER) {
+        mInputManager->getChoreographer().setStylusPointerIconEnabled(enabled);
+        return;
+    }
+
     { // acquire lock
         std::scoped_lock _l(mLock);
 
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 0349ad9..e8201fd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -17,6 +17,10 @@
 package com.android.server.notification;
 
 import static android.app.AutomaticZenRule.TYPE_BEDTIME;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED;
 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
@@ -141,6 +145,8 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@@ -2694,6 +2700,221 @@
         assertEquals(TRIGGER_DESC, actual.getTriggerDescription());
     }
 
+    @Test
+    public void testUpdateAutomaticRule_disabled_triggersBroadcast() throws Exception {
+        setupZenConfig();
+
+        // Add a new automatic zen rule that's enabled
+        AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                null,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                zenRule, "test", Process.SYSTEM_UID, true);
+
+        CountDownLatch latch = new CountDownLatch(1);
+        final int[] actualStatus = new int[1];
+        ZenModeHelper.Callback callback = new ZenModeHelper.Callback() {
+            @Override
+            void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
+                if (Objects.equals(createdId, id)) {
+                    actualStatus[0] = status;
+                    latch.countDown();
+                }
+            }
+        };
+        mZenModeHelper.addCallback(callback);
+
+        zenRule.setEnabled(false);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, true);
+
+        assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
+        assertEquals(AUTOMATIC_RULE_STATUS_DISABLED, actualStatus[0]);
+    }
+
+    @Test
+    public void testUpdateAutomaticRule_enabled_triggersBroadcast() throws Exception {
+        setupZenConfig();
+
+        // Add a new automatic zen rule that's enabled
+        AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                null,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, false);
+        final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                zenRule, "test", Process.SYSTEM_UID, true);
+
+        CountDownLatch latch = new CountDownLatch(1);
+        final int[] actualStatus = new int[1];
+        ZenModeHelper.Callback callback = new ZenModeHelper.Callback() {
+            @Override
+            void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
+                if (Objects.equals(createdId, id)) {
+                    actualStatus[0] = status;
+                    latch.countDown();
+                }
+            }
+        };
+        mZenModeHelper.addCallback(callback);
+
+        zenRule.setEnabled(true);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, true);
+
+        assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
+        assertEquals(AUTOMATIC_RULE_STATUS_ENABLED, actualStatus[0]);
+    }
+
+    @Test
+    public void testUpdateAutomaticRule_activated_triggersBroadcast() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        setupZenConfig();
+
+        // Add a new automatic zen rule that's enabled
+        AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                null,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                zenRule, "test", Process.SYSTEM_UID, true);
+
+        CountDownLatch latch = new CountDownLatch(1);
+        final int[] actualStatus = new int[1];
+        ZenModeHelper.Callback callback = new ZenModeHelper.Callback() {
+            @Override
+            void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
+                if (Objects.equals(createdId, id)) {
+                    actualStatus[0] = status;
+                    latch.countDown();
+                }
+            }
+        };
+        mZenModeHelper.addCallback(callback);
+
+        mZenModeHelper.setAutomaticZenRuleState(createdId,
+                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+                Process.SYSTEM_UID, true);
+
+        assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
+        assertEquals(AUTOMATIC_RULE_STATUS_ACTIVATED, actualStatus[0]);
+    }
+
+    @Test
+    public void testUpdateAutomaticRule_deactivatedByUser_triggersBroadcast() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        setupZenConfig();
+
+        // Add a new automatic zen rule that's enabled
+        AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                null,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                zenRule, "test", Process.SYSTEM_UID, true);
+
+        CountDownLatch latch = new CountDownLatch(1);
+        final int[] actualStatus = new int[2];
+        ZenModeHelper.Callback callback = new ZenModeHelper.Callback() {
+            int i = 0;
+            @Override
+            void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
+                if (Objects.equals(createdId, id)) {
+                    actualStatus[i++] = status;
+                    latch.countDown();
+                }
+            }
+        };
+        mZenModeHelper.addCallback(callback);
+
+        mZenModeHelper.setAutomaticZenRuleState(createdId,
+                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+                Process.SYSTEM_UID, true);
+
+        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
+                Process.SYSTEM_UID, true);
+
+        assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
+        assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]);
+    }
+
+    @Test
+    public void testUpdateAutomaticRule_deactivatedByApp_triggersBroadcast() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        setupZenConfig();
+
+        // Add a new automatic zen rule that's enabled
+        AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                null,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                zenRule, "test", Process.SYSTEM_UID, true);
+
+        CountDownLatch latch = new CountDownLatch(1);
+        final int[] actualStatus = new int[2];
+        ZenModeHelper.Callback callback = new ZenModeHelper.Callback() {
+            int i = 0;
+            @Override
+            void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
+                if (Objects.equals(createdId, id)) {
+                    actualStatus[i++] = status;
+                    latch.countDown();
+                }
+            }
+        };
+        mZenModeHelper.addCallback(callback);
+
+        mZenModeHelper.setAutomaticZenRuleState(createdId,
+                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+                Process.SYSTEM_UID, true);
+
+        mZenModeHelper.setAutomaticZenRuleState(createdId,
+                new Condition(zenRule.getConditionId(), "", STATE_FALSE),
+                Process.SYSTEM_UID, true);
+
+        assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
+        assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]);
+    }
+
+    @Test
+    public void testUpdateAutomaticRule_unsnoozes() throws IllegalArgumentException {
+        setupZenConfig();
+
+        // Add a new automatic zen rule that's enabled
+        AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                null,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                zenRule, "test", Process.SYSTEM_UID, true);
+
+        // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
+        mZenModeHelper.setAutomaticZenRuleState(createdId,
+                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+                Process.SYSTEM_UID, true);
+
+        // Event 2: Snooze rule by turning off DND
+        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
+                Process.SYSTEM_UID, true);
+
+        // Event 3: "User" turns off the automatic rule (sets it to not enabled)
+        zenRule.setEnabled(false);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, true);
+
+        assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing);
+    }
+
     private void setupZenConfig() {
         mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF;
         mZenModeHelper.mConfig.allowAlarms = false;