Merge "Fix broken documentation in android.view" into main
diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline
index 48c7657..c28480e 100644
--- a/api/javadoc-lint-baseline
+++ b/api/javadoc-lint-baseline
@@ -142,8 +142,6 @@
 android/media/AudioManager.java:311: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
 android/media/AudioManager.java:313: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
 android/media/AudioMetadata.java:118: lint: Unresolved link/see tag "android.media.AudioPresentation.ContentClassifier One of {@link android.media.AudioPresentation#CONTENT_UNKNOWN AudioPresentation#CONTENT_UNKNOWN},     {@link android.media.AudioPresentation#CONTENT_MAIN AudioPresentation#CONTENT_MAIN},     {@link android.media.AudioPresentation#CONTENT_MUSIC_AND_EFFECTS AudioPresentation#CONTENT_MUSIC_AND_EFFECTS},     {@link android.media.AudioPresentation#CONTENT_VISUALLY_IMPAIRED AudioPresentation#CONTENT_VISUALLY_IMPAIRED},     {@link android.media.AudioPresentation#CONTENT_HEARING_IMPAIRED AudioPresentation#CONTENT_HEARING_IMPAIRED},     {@link android.media.AudioPresentation#CONTENT_DIALOG AudioPresentation#CONTENT_DIALOG},     {@link android.media.AudioPresentation#CONTENT_COMMENTARY AudioPresentation#CONTENT_COMMENTARY},     {@link android.media.AudioPresentation#CONTENT_EMERGENCY AudioPresentation#CONTENT_EMERGENCY},     {@link android.media.AudioPresentation#CONTENT_VOICEOVER AudioPresentation#CONTENT_VOICEOVER}." in android.media.AudioMetadata.Format [101]
-android/media/MediaRouter2.java:162: lint: Unresolved link/see tag "#getInstance(android.content.Context,java.lang.String)" in android.media.MediaRouter2 [101]
-android/media/midi/MidiUmpDeviceService.java:-1: lint: Unresolved link/see tag "#MidiDeviceService" in android.media.midi.MidiUmpDeviceService [101]
 android/media/tv/SectionRequest.java:44: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionRequest [101]
 android/media/tv/SectionResponse.java:39: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionResponse [101]
 android/media/tv/TableRequest.java:48: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.TableRequest [101]
@@ -213,12 +211,6 @@
 com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "IdentifierType#DAB_SID_EXT" in android [101]
 com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT" in android [101]
 com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "RadioTuner" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "ComponentName" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "IllegalArgumentException" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "MediaSession#setMediaButtonBroadcastReceiver(ComponentName)" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "IllegalArgumentException" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "MediaSession#setMediaButtonReceiver(PendingIntent)" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "PendingIntent" in android [101]
 com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "Build.VERSION_CODES#S API 31" in android [101]
 com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequireUserAction" in android [101]
 com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101]
diff --git a/core/api/current.txt b/core/api/current.txt
index 9c7dc17..20424be 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -32392,7 +32392,7 @@
     method public void addData(@NonNull String, @Nullable byte[], int);
     method public void addFile(@NonNull String, @NonNull java.io.File, int) throws java.io.IOException;
     method public void addText(@NonNull String, @NonNull String);
-    method @Nullable @RequiresPermission(allOf={"android.permission.READ_DROPBOX_DATA", android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.DropBoxManager.Entry getNextEntry(String, long);
+    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_LOGS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.DropBoxManager.Entry getNextEntry(String, long);
     method public boolean isTagEnabled(String);
     field public static final String ACTION_DROPBOX_ENTRY_ADDED = "android.intent.action.DROPBOX_ENTRY_ADDED";
     field public static final String EXTRA_DROPPED_COUNT = "android.os.extra.DROPPED_COUNT";
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index 109d6b2..cf35460 100644
--- a/core/java/android/os/DropBoxManager.java
+++ b/core/java/android/os/DropBoxManager.java
@@ -17,7 +17,7 @@
 package android.os;
 
 import static android.Manifest.permission.PACKAGE_USAGE_STATS;
-import static android.Manifest.permission.READ_DROPBOX_DATA;
+import static android.Manifest.permission.READ_LOGS;
 
 import android.annotation.BytesLong;
 import android.annotation.CurrentTimeMillisLong;
@@ -81,11 +81,9 @@
 
     /**
      * Broadcast Action: This is broadcast when a new entry is added in the dropbox.
-     * For Android V+ (including V), you must hold the
-     * {@link android.Manifest.permission#READ_DROPBOX_DATA} permission in order
-     * to receive this broadcast. For Android version earlier than
-     * Android V, you must hold {@link android.Manifest.permission#READ_LOGS}.
-     * This broadcast can be rate limited for low priority entries
+     * You must hold the {@link android.Manifest.permission#READ_LOGS} permission
+     * in order to receive this broadcast. This broadcast can be rate limited for low priority
+     * entries
      *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
@@ -384,16 +382,12 @@
     /**
      * Gets the next entry from the drop box <em>after</em> the specified time.
      * You must always call {@link Entry#close()} on the return value!
-     * {@link android.Manifest.permission#READ_DROPBOX_DATA} permission is
-     * required for Android V or later.
-     * {@link android.Manifest.permission#READ_LOGS} permission is
-     * required for Android earlier than V.
      *
      * @param tag of entry to look for, null for all tags
      * @param msec time of the last entry seen
      * @return the next entry, or null if there are no more entries
      */
-    @RequiresPermission(allOf = { READ_DROPBOX_DATA, PACKAGE_USAGE_STATS })
+    @RequiresPermission(allOf = { READ_LOGS, PACKAGE_USAGE_STATS })
     public @Nullable Entry getNextEntry(String tag, long msec) {
         try {
             return mService.getNextEntryWithAttribution(tag, msec, mContext.getOpPackageName(),
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 58376a7..0801dd8 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -62,16 +62,14 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributes;
+ import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Deque;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.Predicate;
-import java.util.regex.Pattern;
 
 /**
  * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local
@@ -89,6 +87,8 @@
             DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER,
             DocumentsContract.QUERY_ARG_MIME_TYPES);
 
+    private static final int MAX_RESULTS_NUMBER = 23;
+
     private static String joinNewline(String... args) {
         return TextUtils.join("\n", args);
     }
@@ -375,62 +375,53 @@
     }
 
     /**
-     * This method is similar to
-     * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns
-     * all children documents including hidden directories/files.
-     *
-     * <p>
-     * In a scoped storage world, access to "Android/data" style directories are hidden for privacy
-     * reasons. This method may show privacy sensitive data, so its usage should only be in
-     * restricted modes.
-     *
-     * @param parentDocumentId the directory to return children for.
-     * @param projection list of {@link Document} columns to put into the
-     *            cursor. If {@code null} all supported columns should be
-     *            included.
-     * @param sortOrder how to order the rows, formatted as an SQL
-     *            {@code ORDER BY} clause (excluding the ORDER BY itself).
-     *            Passing {@code null} will use the default sort order, which
-     *            may be unordered. This ordering is a hint that can be used to
-     *            prioritize how data is fetched from the network, but UI may
-     *            always enforce a specific ordering
-     * @throws FileNotFoundException when parent document doesn't exist or query fails
+     * WARNING: this method should really be {@code final}, but for the backward compatibility it's
+     * not; new classes that extend {@link FileSystemProvider} should override
+     * {@link #queryChildDocuments(String, String[], String, boolean)}, not this method.
      */
-    protected Cursor queryChildDocumentsShowAll(
-            String parentDocumentId, String[] projection, String sortOrder)
-            throws FileNotFoundException {
-        return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true);
-    }
-
     @Override
-    public Cursor queryChildDocuments(
-            String parentDocumentId, String[] projection, String sortOrder)
+    public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder)
             throws FileNotFoundException {
-        // Access to some directories is hidden for privacy reasons.
-        return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow);
+        return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ false);
     }
 
-    private Cursor queryChildDocuments(
-            String parentDocumentId, String[] projection, String sortOrder,
-            @NonNull Predicate<File> filter) throws FileNotFoundException {
-        final File parent = getFileForDocId(parentDocumentId);
-        final MatrixCursor result = new DirectoryCursor(
-                resolveProjection(projection), parentDocumentId, parent);
+    /**
+     * This method is similar to {@link #queryChildDocuments(String, String[], String)}, however, it
+     * could return <b>all</b> content of the directory, <b>including restricted (hidden)
+     * directories and files</b>.
+     * <p>
+     * In the scoped storage world, some directories and files (e.g. {@code Android/data/} and
+     * {@code Android/obb/} on the external storage) are hidden for privacy reasons.
+     * Hence, this method may reveal privacy-sensitive data, thus should be used with extra care.
+     */
+    @Override
+    public final Cursor queryChildDocumentsForManage(String documentId, String[] projection,
+            String sortOrder) throws FileNotFoundException {
+        return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true);
+    }
 
-        if (!filter.test(parent)) {
-            Log.w(TAG, "No permission to access parentDocumentId: " + parentDocumentId);
+    protected Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder,
+            boolean includeHidden) throws FileNotFoundException {
+        final File parent = getFileForDocId(documentId);
+        final MatrixCursor result = new DirectoryCursor(
+                resolveProjection(projection), documentId, parent);
+
+        if (!parent.isDirectory()) {
+            Log.w(TAG, '"' + documentId + "\" is not a directory");
             return result;
         }
 
-        if (parent.isDirectory()) {
-            for (File file : FileUtils.listFilesOrEmpty(parent)) {
-                if (filter.test(file)) {
-                    includeFile(result, null, file);
-                }
-            }
-        } else {
-            Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory");
+        if (!includeHidden && shouldHideDocument(documentId)) {
+            Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden");
+            return result;
         }
+
+        for (File file : FileUtils.listFilesOrEmpty(parent)) {
+            if (!includeHidden && shouldHideDocument(file)) continue;
+
+            includeFile(result, null, file);
+        }
+
         return result;
     }
 
@@ -452,23 +443,29 @@
      *
      * @see ContentResolver#EXTRA_HONORED_ARGS
      */
-    protected final Cursor querySearchDocuments(
-            File folder, String[] projection, Set<String> exclusion, Bundle queryArgs)
-            throws FileNotFoundException {
+    protected final Cursor querySearchDocuments(File folder, String[] projection,
+            Set<String> exclusion, Bundle queryArgs) throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(resolveProjection(projection));
-        final List<File> pending = new ArrayList<>();
-        pending.add(folder);
-        while (!pending.isEmpty() && result.getCount() < 24) {
-            final File file = pending.remove(0);
-            if (shouldHide(file)) continue;
+
+        // We'll be a running a BFS here.
+        final Queue<File> pending = new ArrayDeque<>();
+        pending.offer(folder);
+
+        while (!pending.isEmpty() && result.getCount() < MAX_RESULTS_NUMBER) {
+            final File file = pending.poll();
+
+            // Skip hidden documents (both files and directories)
+            if (shouldHideDocument(file)) continue;
 
             if (file.isDirectory()) {
                 for (File child : FileUtils.listFilesOrEmpty(file)) {
-                    pending.add(child);
+                    pending.offer(child);
                 }
             }
-            if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file,
-                    queryArgs)) {
+
+            if (exclusion.contains(file.getAbsolutePath())) continue;
+
+            if (matchSearchQueryArguments(file, queryArgs)) {
                 includeFile(result, null, file);
             }
         }
@@ -612,26 +609,23 @@
 
         final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS);
         if (flagIndex != -1) {
+            final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR);
             int flags = 0;
             if (file.canWrite()) {
-                if (mimeType.equals(Document.MIME_TYPE_DIR)) {
+                flags |= Document.FLAG_SUPPORTS_DELETE;
+                flags |= Document.FLAG_SUPPORTS_RENAME;
+                flags |= Document.FLAG_SUPPORTS_MOVE;
+                if (isDir) {
                     flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
-                    flags |= Document.FLAG_SUPPORTS_DELETE;
-                    flags |= Document.FLAG_SUPPORTS_RENAME;
-                    flags |= Document.FLAG_SUPPORTS_MOVE;
-
-                    if (shouldBlockFromTree(docId)) {
-                        flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
-                    }
-
                 } else {
                     flags |= Document.FLAG_SUPPORTS_WRITE;
-                    flags |= Document.FLAG_SUPPORTS_DELETE;
-                    flags |= Document.FLAG_SUPPORTS_RENAME;
-                    flags |= Document.FLAG_SUPPORTS_MOVE;
                 }
             }
 
+            if (isDir && shouldBlockDirectoryFromTree(docId)) {
+                flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
+            }
+
             if (mimeType.startsWith("image/")) {
                 flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
             }
@@ -664,22 +658,36 @@
         return row;
     }
 
-    private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile(
-            "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$");
+    /**
+     * Some providers may want to restrict access to certain directories and files,
+     * e.g. <i>"Android/data"</i> and <i>"Android/obb"</i> on the shared storage for
+     * privacy reasons.
+     * Such providers should override this method.
+     */
+    protected boolean shouldHideDocument(@NonNull String documentId)
+            throws FileNotFoundException {
+        return false;
+    }
 
     /**
-     * In a scoped storage world, access to "Android/data" style directories are
-     * hidden for privacy reasons.
+     * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of
+     * a {@link String} {@code documentId}.
+     *
+     * @see #shouldHideDocument(String)
      */
-    protected boolean shouldHide(@NonNull File file) {
-        return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches());
+    protected final boolean shouldHideDocument(@NonNull File document)
+            throws FileNotFoundException {
+        return shouldHideDocument(getDocIdForFile(document));
     }
 
-    private boolean shouldShow(@NonNull File file) {
-        return !shouldHide(file);
-    }
-
-    protected boolean shouldBlockFromTree(@NonNull String docId) {
+    /**
+     * @return if the directory that should be blocked from being selected when the user launches
+     * an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} intent.
+     *
+     * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
+     */
+    protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId)
+            throws FileNotFoundException {
         return false;
     }
 
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b5d70d3..50253cf 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -1315,7 +1315,16 @@
         ALOGI("VM exiting with result code %d.", code);
         onExit(code);
     }
+
+#ifdef __ANDROID_CLANG_COVERAGE__
+    // When compiled with coverage, a function is registered with atexit to call
+    // `__llvm_profile_write_file` when the process exit.
+    // For Clang code coverage to work, call exit instead of _exit to run hooks
+    // registered with atexit.
+    ::exit(code);
+#else
     ::_exit(code);
+#endif
 }
 
 void AndroidRuntime::onVmCreated(JNIEnv* env)
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e120f82..ca768ad 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4565,12 +4565,6 @@
     <permission android:name="android.permission.SET_DEBUG_APP"
         android:protectionLevel="signature|privileged|development" />
 
-    <!-- Allows an application to access the data in Dropbox.
-    <p>Not for use by third-party applications.
-    @hide -->
-    <permission android:name="android.permission.READ_DROPBOX_DATA"
-        android:protectionLevel="signature|privileged|development" />
-
     <!-- Allows an application to set the maximum number of (not needed)
          application processes that can be running.
          <p>Not for use by third-party applications. -->
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
index b13e9a2..1df1136 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -81,9 +81,7 @@
         <option name="shell-timeout" value="6600s"/>
         <option name="test-timeout" value="6000s"/>
         <option name="hidden-api-checks" value="false"/>
-        <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed
         <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
-        -->
         <!-- PerfettoListener related arguments -->
         <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
         <option name="instrumentation-arg"
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 8c63580..2169090 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -463,7 +463,7 @@
     /**
      * Returns the current {@link RouteListingPreference} of the target router.
      *
-     * <p>If this instance was created using {@link #getInstance(Context, String)}, then it returns
+     * <p>If this instance was created using {@code #getInstance(Context, String)}, then it returns
      * the last {@link RouteListingPreference} set by the process this router was created for.
      *
      * @see #setRouteListingPreference(RouteListingPreference)
diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java
index 6e2aaab..bbbe7f6 100644
--- a/media/java/android/media/midi/MidiUmpDeviceService.java
+++ b/media/java/android/media/midi/MidiUmpDeviceService.java
@@ -38,7 +38,7 @@
  * of {@link MidiReceiver}s for sending data out the output ports.
  *
  * Unlike traditional MIDI byte streams, only complete UMPs should be sent.
- * Unlike with {@link #MidiDeviceService}, the number of input and output ports must be equal.
+ * Unlike with {@link MidiDeviceService}, the number of input and output ports must be equal.
  *
  * <p>To extend this class, you must declare the service in your manifest file with
  * an intent filter with the {@link #SERVICE_INTERFACE} action
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index d294601..80e2247 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -156,4 +156,24 @@
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     void setUserReviewGrantedConsentResult(ReviewGrantedConsentResult consentResult,
             in @nullable IMediaProjection projection);
+
+    /**
+     * Notifies system server that we are handling a particular state during the consent flow.
+     *
+     * <p>Only used for emitting atoms.
+     *
+     * @param hostUid               The uid of the process requesting consent to capture, may be an app or
+     *                              SystemUI.
+     * @param state                 The state that SystemUI is handling during the consent flow.
+     *                              Must be a valid
+     *                              state defined in the MediaProjectionState enum.
+     * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
+     *                              Indicates the entry point for requesting the permission. Must be
+     *                              a valid state defined
+     *                              in the SessionCreationSource enum.
+     */
+    @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
+    void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource);
 }
diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp
index 58bcd1d..e71597a 100644
--- a/omapi/aidl/Android.bp
+++ b/omapi/aidl/Android.bp
@@ -24,6 +24,11 @@
     backend: {
         java: {
             sdk_version: "module_current",
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.nfcservices",
+            ],
+
         },
         rust: {
             enabled: true,
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 4c313b2..3409c29 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.externalstorage;
 
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.usage.StorageStatsManager;
@@ -64,7 +66,19 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.regex.Pattern;
 
+/**
+ * Presents content of the shared (a.k.a. "external") storage.
+ * <p>
+ * Starting with Android 11 (R), restricts access to the certain sections of the shared storage:
+ * {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/}, that will be hidden in
+ * the DocumentsUI by default.
+ * See <a href="https://developer.android.com/about/versions/11/privacy/storage">
+ * Storage updates in Android 11</a>.
+ * <p>
+ * Documents ID format: {@code root:path/to/file}.
+ */
 public class ExternalStorageProvider extends FileSystemProvider {
     private static final String TAG = "ExternalStorage";
 
@@ -75,7 +89,12 @@
     private static final Uri BASE_URI =
             new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build();
 
-    // docId format: root:path/to/file
+    /**
+     * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and
+     * {@code /Android/sandbox/} along with all their subdirectories and content.
+     */
+    private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES =
+            Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE);
 
     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
             Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
@@ -278,76 +297,91 @@
         return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
     }
 
+    /**
+     * Mark {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/} on the
+     * integrated shared ("external") storage along with all their content and subdirectories as
+     * hidden.
+     */
     @Override
-    public Cursor queryChildDocumentsForManage(
-            String parentDocId, String[] projection, String sortOrder)
-            throws FileNotFoundException {
-        return queryChildDocumentsShowAll(parentDocId, projection, sortOrder);
+    protected boolean shouldHideDocument(@NonNull String documentId) {
+        // Don't need to hide anything on USB drives.
+        if (isOnRemovableUsbStorage(documentId)) {
+            return false;
+        }
+
+        final String path = getPathFromDocId(documentId);
+        return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches();
     }
 
     /**
      * Check that the directory is the root of storage or blocked file from tree.
+     * <p>
+     * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear
+     * the UI, but the user <b>WILL NOT</b> be able to select them.
      *
-     * @param docId the docId of the directory to be checked
+     * @param documentId the docId of the directory to be checked
      * @return true, should be blocked from tree. Otherwise, false.
+     *
+     * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
      */
     @Override
-    protected boolean shouldBlockFromTree(@NonNull String docId) {
-        try {
-            final File dir = getFileForDocId(docId, false /* visible */);
-
-            // the file is null or it is not a directory
-            if (dir == null || !dir.isDirectory()) {
-                return false;
-            }
-
-            // Allow all directories on USB, including the root.
-            try {
-                RootInfo rootInfo = getRootFromDocId(docId);
-                if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) {
-                    return false;
-                }
-            } catch (FileNotFoundException e) {
-                Log.e(TAG, "Failed to determine rootInfo for docId");
-            }
-
-            final String path = getPathFromDocId(docId);
-
-            // Block the root of the storage
-            if (path.isEmpty()) {
-                return true;
-            }
-
-            // Block Download folder from tree
-            if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(Locale.ROOT),
-                    path.toLowerCase(Locale.ROOT))) {
-                return true;
-            }
-
-            // Block /Android
-            if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(Locale.ROOT),
-                    path.toLowerCase(Locale.ROOT))) {
-                return true;
-            }
-
-            // Block /Android/data, /Android/obb, /Android/sandbox and sub dirs
-            if (shouldHide(dir)) {
-                return true;
-            }
-
+    protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId)
+            throws FileNotFoundException {
+        final File dir = getFileForDocId(documentId, false);
+        // The file is null or it is not a directory
+        if (dir == null || !dir.isDirectory()) {
             return false;
-        } catch (IOException e) {
-            throw new IllegalArgumentException(
-                    "Failed to determine if " + docId + " should block from tree " + ": " + e);
         }
+
+        // Allow all directories on USB, including the root.
+        if (isOnRemovableUsbStorage(documentId)) {
+            return false;
+        }
+
+        // Get canonical(!) path. Note that this path will have neither leading nor training "/".
+        // This the root's path will be just an empty string.
+        final String path = getPathFromDocId(documentId);
+
+        // Block the root of the storage
+        if (path.isEmpty()) {
+            return true;
+        }
+
+        // Block /Download/ and /Android/ folders from the tree.
+        if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) ||
+                equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) {
+            return true;
+        }
+
+        // This shouldn't really make a difference, but just in case - let's block hidden
+        // directories as well.
+        if (shouldHideDocument(documentId)) {
+            return true;
+        }
+
+        return false;
     }
 
+    private boolean isOnRemovableUsbStorage(@NonNull String documentId) {
+        final RootInfo rootInfo;
+        try {
+            rootInfo = getRootFromDocId(documentId);
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Failed to determine rootInfo for docId\"" + documentId + '"');
+            return false;
+        }
+
+        return (rootInfo.flags & Root.FLAG_REMOVABLE_USB) != 0;
+    }
+
+    @NonNull
     @Override
-    protected String getDocIdForFile(File file) throws FileNotFoundException {
+    protected String getDocIdForFile(@NonNull File file) throws FileNotFoundException {
         return getDocIdForFileMaybeCreate(file, false);
     }
 
-    private String getDocIdForFileMaybeCreate(File file, boolean createNewDir)
+    @NonNull
+    private String getDocIdForFileMaybeCreate(@NonNull File file, boolean createNewDir)
             throws FileNotFoundException {
         String path = file.getAbsolutePath();
 
@@ -417,31 +451,33 @@
     private File getFileForDocId(String docId, boolean visible, boolean mustExist)
             throws FileNotFoundException {
         RootInfo root = getRootFromDocId(docId);
-        return buildFile(root, docId, visible, mustExist);
+        return buildFile(root, docId, mustExist);
     }
 
-    private Pair<RootInfo, File> resolveDocId(String docId, boolean visible)
-            throws FileNotFoundException {
+    private Pair<RootInfo, File> resolveDocId(String docId) throws FileNotFoundException {
         RootInfo root = getRootFromDocId(docId);
-        return Pair.create(root, buildFile(root, docId, visible, true));
+        return Pair.create(root, buildFile(root, docId, /* mustExist */ true));
     }
 
     @VisibleForTesting
-    static String getPathFromDocId(String docId) throws IOException {
+    static String getPathFromDocId(String docId) {
         final int splitIndex = docId.indexOf(':', 1);
         final String docIdPath = docId.substring(splitIndex + 1);
-        // Get CanonicalPath and remove the first "/"
-        final String canonicalPath = new File(docIdPath).getCanonicalPath().substring(1);
 
-        if (canonicalPath.isEmpty()) {
-            return canonicalPath;
+        // Canonicalize path and strip the leading "/"
+        final String path;
+        try {
+            path = new File(docIdPath).getCanonicalPath().substring(1);
+        } catch (IOException e) {
+            Log.w(TAG, "Could not canonicalize \"" + docIdPath + '"');
+            return "";
         }
 
-        // remove trailing "/"
-        if (canonicalPath.charAt(canonicalPath.length() - 1) == '/') {
-            return canonicalPath.substring(0, canonicalPath.length() - 1);
+        // Remove the trailing "/" as well.
+        if (!path.isEmpty() && path.charAt(path.length() - 1) == '/') {
+            return path.substring(0, path.length() - 1);
         } else {
-            return canonicalPath;
+            return path;
         }
     }
 
@@ -460,7 +496,7 @@
         return root;
     }
 
-    private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist)
+    private File buildFile(RootInfo root, String docId, boolean mustExist)
             throws FileNotFoundException {
         final int splitIndex = docId.indexOf(':', 1);
         final String path = docId.substring(splitIndex + 1);
@@ -544,7 +580,7 @@
     @Override
     public Path findDocumentPath(@Nullable String parentDocId, String childDocId)
             throws FileNotFoundException {
-        final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false);
+        final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId);
         final RootInfo root = resolvedDocId.first;
         File child = resolvedDocId.second;
 
@@ -648,6 +684,13 @@
         }
     }
 
+    /**
+     * Print the state into the given stream.
+     * Gets invoked when you run:
+     * <pre>
+     * adb shell dumpsys activity provider com.android.externalstorage/.ExternalStorageProvider
+     * </pre>
+     */
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160);
@@ -731,4 +774,8 @@
         }
         return bundle;
     }
+
+    private static boolean equalIgnoringCase(@NonNull String a, @NonNull String b) {
+        return TextUtils.equals(a.toLowerCase(Locale.ROOT), b.toLowerCase(Locale.ROOT));
+    }
 }
diff --git a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
index 18a8edc..0144b6e 100644
--- a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
+++ b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
@@ -16,47 +16,64 @@
 
 package com.android.externalstorage;
 
+import static android.provider.DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID;
+
 import static com.android.externalstorage.ExternalStorageProvider.AUTHORITY;
 import static com.android.externalstorage.ExternalStorageProvider.getPathFromDocId;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.app.Instrumentation;
+import android.content.Context;
 import android.content.pm.ProviderInfo;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 public class ExternalStorageProviderTest {
+
+    @NonNull
+    private static final Instrumentation sInstrumentation =
+            InstrumentationRegistry.getInstrumentation();
+    @NonNull
+    private static final Context sTargetContext = sInstrumentation.getTargetContext();
+
+    private ExternalStorageProvider mExternalStorageProvider;
+
+    @Before
+    public void setUp() {
+        mExternalStorageProvider = new ExternalStorageProvider();
+    }
+
+
     @Test
-    public void onCreate_shouldUpdateVolumes() throws Exception {
-        ExternalStorageProvider externalStorageProvider = new ExternalStorageProvider();
-        ExternalStorageProvider spyProvider = spy(externalStorageProvider);
-        ProviderInfo providerInfo = new ProviderInfo();
+    public void onCreate_shouldUpdateVolumes() {
+        final ExternalStorageProvider spyProvider = spy(mExternalStorageProvider);
+
+        final ProviderInfo providerInfo = new ProviderInfo();
         providerInfo.authority = AUTHORITY;
         providerInfo.grantUriPermissions = true;
         providerInfo.exported = true;
 
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                spyProvider.attachInfoForTesting(
-                        InstrumentationRegistry.getTargetContext(), providerInfo);
-            }
-        });
+        sInstrumentation.runOnMainSync(() ->
+                spyProvider.attachInfoForTesting(sTargetContext, providerInfo));
 
         verify(spyProvider, atLeast(1)).updateVolumes();
     }
 
     @Test
-    public void testGetPathFromDocId() throws Exception {
+    public void test_getPathFromDocId() {
         final String root = "root";
         final String path = "abc/def/ghi";
         String docId = root + ":" + path;
@@ -79,4 +96,62 @@
         docId = root + ":" + twoDotPath;
         assertEquals(getPathFromDocId(docId), path);
     }
+
+    @Test
+    public void test_shouldHideDocument() {
+        // Should hide "Android/data", "Android/obb", "Android/sandbox" and all their
+        // "subtrees".
+        final String[] shouldHide = {
+                // "Android/data" and all its subdirectories
+                "Android/data",
+                "Android/data/com.my.app",
+                "Android/data/com.my.app/cache",
+                "Android/data/com.my.app/cache/image.png",
+                "Android/data/mydata",
+
+                // "Android/obb" and all its subdirectories
+                "Android/obb",
+                "Android/obb/com.my.app",
+                "Android/obb/com.my.app/file.blob",
+
+                // "Android/sandbox" and all its subdirectories
+                "Android/sandbox",
+                "Android/sandbox/com.my.app",
+
+                // Also make sure we are not allowing path traversals
+                "Android/./data",
+                "Android/Download/../data",
+        };
+        for (String path : shouldHide) {
+            final String docId = buildDocId(path);
+            assertTrue("ExternalStorageProvider should hide \"" + docId + "\", but it didn't",
+                    mExternalStorageProvider.shouldHideDocument(docId));
+        }
+
+        // Should NOT hide anything else.
+        final String[] shouldNotHide = {
+                "Android",
+                "Android/datadir",
+                "Documents",
+                "Download",
+                "Music",
+                "Pictures",
+        };
+        for (String path : shouldNotHide) {
+            final String docId = buildDocId(path);
+            assertFalse("ExternalStorageProvider should NOT hide \"" + docId + "\", but it did",
+                    mExternalStorageProvider.shouldHideDocument(docId));
+        }
+    }
+
+    @NonNull
+    private static String buildDocId(@NonNull String path) {
+        return buildDocId(EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID, path);
+    }
+
+    @NonNull
+    private static String buildDocId(@NonNull String root, @NonNull String path) {
+        // docId format: root:path/to/file
+        return root + ':' + path;
+    }
 }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f65f5a3..4f55e8b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -62,6 +62,7 @@
             static_libs: [
                 "CommunalLayoutLib",
                 "PlatformComposeCore",
+                "PlatformComposeSceneTransitionLayout",
 
                 "androidx.compose.runtime_runtime",
                 "androidx.compose.material3_material3",
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index e4426fe..796abf4b 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -33,6 +33,7 @@
     static_libs: [
         "SystemUI-core",
         "PlatformComposeCore",
+        "PlatformComposeSceneTransitionLayout",
 
         "androidx.compose.runtime_runtime",
         "androidx.compose.animation_animation-graphics",
diff --git a/packages/SystemUI/compose/scene/Android.bp b/packages/SystemUI/compose/scene/Android.bp
new file mode 100644
index 0000000..050d1d5
--- /dev/null
+++ b/packages/SystemUI/compose/scene/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+    name: "PlatformComposeSceneTransitionLayout",
+    manifest: "AndroidManifest.xml",
+
+    srcs: [
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.material3_material3",
+    ],
+
+    kotlincflags: ["-Xjvm-default=all"],
+    use_resource_processor: true,
+}
diff --git a/packages/SystemUI/compose/scene/AndroidManifest.xml b/packages/SystemUI/compose/scene/AndroidManifest.xml
new file mode 100644
index 0000000..81131bb
--- /dev/null
+++ b/packages/SystemUI/compose/scene/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.compose.animation.scene">
+
+
+</manifest>
diff --git a/packages/SystemUI/compose/scene/OWNERS b/packages/SystemUI/compose/scene/OWNERS
new file mode 100644
index 0000000..33a59c2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/OWNERS
@@ -0,0 +1,13 @@
+set noparent
+
+# Bug component: 1184816
+
+jdemeulenaere@google.com
+omarmt@google.com
+
+# SysUI Dr No's.
+# Don't send reviews here.
+dsandler@android.com
+cinek@google.com
+juliacr@google.com
+pixel@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/TEST_MAPPING b/packages/SystemUI/compose/scene/TEST_MAPPING
new file mode 100644
index 0000000..f644a23
--- /dev/null
+++ b/packages/SystemUI/compose/scene/TEST_MAPPING
@@ -0,0 +1,48 @@
+{
+  "presubmit": [
+    {
+      "name": "PlatformComposeSceneTransitionLayoutTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "PlatformComposeCoreTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "SystemUIComposeFeaturesTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "SystemUIComposeGalleryTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
similarity index 71%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 566967f..041fc48 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -17,12 +17,10 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.DisposableEffectResult
-import androidx.compose.runtime.DisposableEffectScope
 import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.lerp
 import androidx.compose.ui.unit.Dp
@@ -45,6 +43,20 @@
 }
 
 /**
+ * Animate a shared Int value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedIntAsState(
+    value: Int,
+    debugName: String,
+    canOverflow: Boolean = true,
+): State<Int> {
+    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
  * Animate a shared Float value.
  *
  * @see SceneScope.animateSharedValueAsState
@@ -60,6 +72,20 @@
 }
 
 /**
+ * Animate a shared Float value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedFloatAsState(
+    value: Float,
+    debugName: String,
+    canOverflow: Boolean = true,
+): State<Float> {
+    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
  * Animate a shared Dp value.
  *
  * @see SceneScope.animateSharedValueAsState
@@ -75,6 +101,20 @@
 }
 
 /**
+ * Animate a shared Dp value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedDpAsState(
+    value: Dp,
+    debugName: String,
+    canOverflow: Boolean = true,
+): State<Dp> {
+    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
  * Animate a shared Color value.
  *
  * @see SceneScope.animateSharedValueAsState
@@ -88,6 +128,19 @@
     return animateSharedValueAsState(value, key, element, ::lerp, canOverflow = false)
 }
 
+/**
+ * Animate a shared Color value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedColorAsState(
+    value: Color,
+    debugName: String,
+): State<Color> {
+    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow = false)
+}
+
 @Composable
 internal fun <T> animateSharedValueAsState(
     layoutImpl: SceneTransitionLayoutImpl,
@@ -98,33 +151,22 @@
     lerp: (T, T, Float) -> T,
     canOverflow: Boolean,
 ): State<T> {
-    val sharedValue = remember(key) { Element.SharedValue(key, value) }
+    val sharedValue =
+        Snapshot.withoutReadObservation {
+            element.sceneValues.getValue(scene.key).sharedValues.getOrPut(key) {
+                Element.SharedValue(key, value)
+            } as Element.SharedValue<T>
+        }
+
     if (value != sharedValue.value) {
         sharedValue.value = value
     }
 
-    DisposableEffect(element, scene, sharedValue) {
-        addSharedValueToElement(element, scene, sharedValue)
-    }
-
     return remember(layoutImpl, element, sharedValue, lerp, canOverflow) {
         derivedStateOf { computeValue(layoutImpl, element, sharedValue, lerp, canOverflow) }
     }
 }
 
-private fun <T> DisposableEffectScope.addSharedValueToElement(
-    element: Element,
-    scene: Scene,
-    sharedValue: Element.SharedValue<T>,
-): DisposableEffectResult {
-    val sceneValues =
-        element.sceneValues[scene.key] ?: error("Element $element is not present in $scene")
-    val sharedValues = sceneValues.sharedValues
-
-    sharedValues[sharedValue.key] = sharedValue
-    return onDispose { sharedValues.remove(sharedValue.key) }
-}
-
 private fun <T> computeValue(
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
similarity index 89%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 11bbf2a..6dbeb69 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -21,6 +21,7 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.Modifier
@@ -36,8 +37,6 @@
 
 private const val TAG = "MovableElement"
 
-private object MovableElementScopeImpl : MovableElementScope
-
 @Composable
 internal fun MovableElement(
     layoutImpl: SceneTransitionLayoutImpl,
@@ -51,6 +50,10 @@
         // every time an element is added/removed from SceneTransitionLayoutImpl.elements, so we
         // disable read observation during the look-up in that map.
         val element = Snapshot.withoutReadObservation { layoutImpl.elements.getValue(key) }
+        val movableElementScope =
+            remember(layoutImpl, element, scene) {
+                MovableElementScopeImpl(layoutImpl, element, scene)
+            }
 
         // The [Picture] to which we save the last drawing commands of this element. This is
         // necessary because the content of this element might not be composed in this scene, in
@@ -77,7 +80,7 @@
                     }
                 }
             ) {
-                element.movableContent { MovableElementScopeImpl.content() }
+                element.movableContent { movableElementScope.content() }
             }
         } else {
             // If we are not composed, we draw the previous drawing commands at the same size as the
@@ -178,3 +181,20 @@
         isHighestScene
     }
 }
+
+private class MovableElementScopeImpl(
+    private val layoutImpl: SceneTransitionLayoutImpl,
+    private val element: Element,
+    private val scene: Scene,
+) : MovableElementScope {
+    @Composable
+    override fun <T> animateSharedValueAsState(
+        value: T,
+        debugName: String,
+        lerp: (start: T, stop: T, fraction: Float) -> T,
+        canOverflow: Boolean,
+    ): State<T> {
+        val key = remember { ValueKey(debugName) }
+        return animateSharedValueAsState(layoutImpl, scene, element, key, value, lerp, canOverflow)
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
similarity index 96%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 4283c0e..74e66d2 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -160,7 +160,16 @@
 
 // TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey
 // arguments to allow sharing values inside a movable element.
-@ElementDsl interface MovableElementScope
+@ElementDsl
+interface MovableElementScope {
+    @Composable
+    fun <T> animateSharedValueAsState(
+        value: T,
+        debugName: String,
+        lerp: (start: T, stop: T, fraction: Float) -> T,
+        canOverflow: Boolean,
+    ): State<T>
+}
 
 /** An action performed by the user. */
 sealed interface UserAction
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp
new file mode 100644
index 0000000..b53fae2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/Android.bp
@@ -0,0 +1,50 @@
+// Copyright (C) 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_test {
+    name: "PlatformComposeSceneTransitionLayoutTests",
+    manifest: "AndroidManifest.xml",
+    test_suites: ["device-tests"],
+    sdk_version: "current",
+    certificate: "platform",
+
+    srcs: [
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "PlatformComposeSceneTransitionLayout",
+
+        "androidx.test.runner",
+        "androidx.test.ext.junit",
+
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.ui_ui-test-junit4",
+        "androidx.compose.ui_ui-test-manifest",
+
+        "truth",
+    ],
+
+    kotlincflags: ["-Xjvm-default=all"],
+    use_resource_processor: true,
+}
diff --git a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
new file mode 100644
index 0000000..1a9172e
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.compose.animation.scene.tests" >
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.compose.animation.scene.tests"
+                     android:label="Tests for SceneTransitionLayout"/>
+
+</manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
new file mode 100644
index 0000000..7b7695e
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 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
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.lerp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.ui.util.lerp
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AnimatedSharedAsStateTest {
+    @get:Rule val rule = createComposeRule()
+
+    private data class Values(
+        val int: Int,
+        val float: Float,
+        val dp: Dp,
+        val color: Color,
+    )
+
+    private fun lerp(start: Values, stop: Values, fraction: Float): Values {
+        return Values(
+            int = lerp(start.int, stop.int, fraction),
+            float = lerp(start.float, stop.float, fraction),
+            dp = lerp(start.dp, stop.dp, fraction),
+            color = lerp(start.color, stop.color, fraction),
+        )
+    }
+
+    @Composable
+    private fun SceneScope.Foo(
+        targetValues: Values,
+        onCurrentValueChanged: (Values) -> Unit,
+    ) {
+        val key = TestElements.Foo
+        Box(Modifier.element(key)) {
+            val int by animateSharedIntAsState(targetValues.int, TestValues.Value1, key)
+            val float by animateSharedFloatAsState(targetValues.float, TestValues.Value2, key)
+            val dp by animateSharedDpAsState(targetValues.dp, TestValues.Value3, key)
+            val color by animateSharedColorAsState(targetValues.color, TestValues.Value4, key)
+
+            // Make sure we read the values during composition, so that we recompose and call
+            // onCurrentValueChanged() with the latest values.
+            val currentValues = Values(int, float, dp, color)
+            SideEffect { onCurrentValueChanged(currentValues) }
+        }
+    }
+
+    @Composable
+    private fun SceneScope.MovableFoo(
+        targetValues: Values,
+        onCurrentValueChanged: (Values) -> Unit,
+    ) {
+        val key = TestElements.Foo
+        MovableElement(key = key, Modifier) {
+            val int by
+                animateSharedIntAsState(targetValues.int, debugName = TestValues.Value1.debugName)
+            val float by
+                animateSharedFloatAsState(
+                    targetValues.float,
+                    debugName = TestValues.Value2.debugName
+                )
+            val dp by
+                animateSharedDpAsState(targetValues.dp, debugName = TestValues.Value3.debugName)
+            val color by
+                animateSharedColorAsState(
+                    targetValues.color,
+                    debugName = TestValues.Value4.debugName
+                )
+
+            // Make sure we read the values during composition, so that we recompose and call
+            // onCurrentValueChanged() with the latest values.
+            val currentValues = Values(int, float, dp, color)
+            SideEffect { onCurrentValueChanged(currentValues) }
+        }
+    }
+
+    @Test
+    fun animateSharedValues() {
+        val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red)
+        val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue)
+
+        var lastValueInFrom = fromValues
+        var lastValueInTo = toValues
+
+        rule.testTransition(
+            fromSceneContent = {
+                Foo(targetValues = fromValues, onCurrentValueChanged = { lastValueInFrom = it })
+            },
+            toSceneContent = {
+                Foo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it })
+            },
+            transition = {
+                // The transition lasts 64ms = 4 frames.
+                spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+            },
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+        ) {
+            before {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+
+                // to was not composed yet, so lastValueInTo was not set yet.
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+
+            at(16) {
+                // Given that we use Modifier.element() here, animateSharedXAsState is composed in
+                // both scenes and values should be interpolated with the transition fraction.
+                val expectedValues = lerp(fromValues, toValues, fraction = 0.25f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            at(32) {
+                val expectedValues = lerp(fromValues, toValues, fraction = 0.5f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            at(48) {
+                val expectedValues = lerp(fromValues, toValues, fraction = 0.75f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            after {
+                assertThat(lastValueInFrom).isEqualTo(toValues)
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+        }
+    }
+
+    @Test
+    fun movableAnimateSharedValues() {
+        val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red)
+        val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue)
+
+        var lastValueInFrom = fromValues
+        var lastValueInTo = toValues
+
+        rule.testTransition(
+            fromSceneContent = {
+                MovableFoo(
+                    targetValues = fromValues,
+                    onCurrentValueChanged = { lastValueInFrom = it }
+                )
+            },
+            toSceneContent = {
+                MovableFoo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it })
+            },
+            transition = {
+                // The transition lasts 64ms = 4 frames.
+                spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+            },
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+        ) {
+            before {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+
+                // to was not composed yet, so lastValueInTo was not set yet.
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+
+            at(16) {
+                // Given that we use MovableElement here, animateSharedXAsState is composed only
+                // once, in the highest scene (in this case, in toScene).
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.25f))
+            }
+
+            at(32) {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.5f))
+            }
+
+            at(48) {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.75f))
+            }
+
+            after {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt
similarity index 94%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt
index 8357262..b4c393e 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt
@@ -37,6 +37,9 @@
 /** Value keys that can be reused by tests. */
 object TestValues {
     val Value1 = ValueKey("Value1")
+    val Value2 = ValueKey("Value2")
+    val Value3 = ValueKey("Value3")
+    val Value4 = ValueKey("Value4")
 }
 
 // We use a transition duration of 480ms here because it is a multiple of 16, the time of a frame in
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
similarity index 99%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
index 2af3638..e94eff3 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
@@ -32,7 +32,6 @@
 import com.android.compose.animation.scene.TestScenes
 import com.android.compose.animation.scene.inScene
 import com.android.compose.animation.scene.testTransition
-import com.android.compose.modifiers.size
 import com.android.compose.test.assertSizeIsEqualTo
 import com.android.compose.test.onEach
 import org.junit.Rule
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index a51c55e..8cfcb68 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -15,8 +15,9 @@
   -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:id="@+id/cd_bottom_sheet"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_height="wrap_content"
     android:gravity="center_horizontal"
     android:orientation="vertical"
     android:paddingHorizontal="@dimen/dialog_side_padding"
@@ -26,11 +27,14 @@
 
     <ImageView
         android:id="@+id/connected_display_dialog_icon"
-        android:layout_width="@dimen/screenrecord_logo_size"
-        android:layout_height="@dimen/screenrecord_logo_size"
+        android:layout_width="@dimen/connected_display_dialog_logo_size"
+        android:layout_height="@dimen/connected_display_dialog_logo_size"
+        android:background="@drawable/circular_background"
+        android:backgroundTint="?androidprv:attr/materialColorPrimary"
         android:importantForAccessibility="no"
+        android:padding="6dp"
         android:src="@drawable/stat_sys_connected_display"
-        android:tint="?androidprv:attr/materialColorPrimary" />
+        android:tint="?androidprv:attr/materialColorOnPrimary" />
 
     <TextView
         android:id="@+id/connected_display_dialog_title"
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index ea3c012..1f671ac 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -37,6 +37,9 @@
 
     <bool name="config_use_large_screen_shade_header">true</bool>
 
+    <!-- Whether to show bottom sheets edge to edge -->
+    <bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
+
     <!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
     string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
     separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1add90f..6856717 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -947,6 +947,9 @@
     <!-- Flag controlling whether visual query attention detection has been enabled. -->
     <bool name="config_enableVisualQueryAttentionDetection">false</bool>
 
+    <!-- Whether to show bottom sheets edge to edge -->
+    <bool name="config_edgeToEdgeBottomSheetDialog">true</bool>
+
     <!--
     Whether the scene container framework is enabled.
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5a83c7d..6377df3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1355,6 +1355,9 @@
     <dimen name="screenrecord_options_padding_bottom">16dp</dimen>
     <dimen name="screenrecord_buttons_margin_top">20dp</dimen>
 
+    <!-- Connected display dialog -->
+    <dimen name="connected_display_dialog_logo_size">48dp</dimen>
+
     <!-- Keyguard user switcher -->
     <dimen name="kg_user_switcher_text_size">16sp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 04a9cae..d57f31f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -67,6 +67,7 @@
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.media.MediaRouter2Manager;
+import android.media.projection.IMediaProjectionManager;
 import android.media.projection.MediaProjectionManager;
 import android.media.session.MediaSessionManager;
 import android.net.ConnectivityManager;
@@ -414,6 +415,13 @@
     }
 
     @Provides
+    @Singleton
+    static IMediaProjectionManager provideIMediaProjectionManager() {
+        return IMediaProjectionManager.Stub.asInterface(
+                ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE));
+    }
+
+    @Provides
     static MediaRouter2Manager provideMediaRouter2Manager(Context context) {
         return MediaRouter2Manager.getInstance(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index 7510cf6c..d19efbd 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -15,14 +15,12 @@
  */
 package com.android.systemui.display.ui.view
 
-import android.app.Dialog
 import android.content.Context
 import android.os.Bundle
-import android.view.Gravity
 import android.view.View
-import android.view.WindowManager
 import android.widget.TextView
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog
 
 /**
  * Dialog used to decide what to do with a connected display.
@@ -35,7 +33,7 @@
     private val onStartMirroringClickListener: View.OnClickListener,
     private val onCancelMirroring: View.OnClickListener,
     theme: Int = R.style.Theme_SystemUI_Dialog,
-) : Dialog(context, theme) {
+) : SystemUIBottomSheetDialog(context, theme) {
 
     private lateinit var mirrorButton: TextView
     private lateinit var dismissButton: TextView
@@ -43,13 +41,8 @@
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        window?.apply {
-            setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
-            addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
-            setGravity(Gravity.BOTTOM)
-        }
         setContentView(R.layout.connected_display_dialog)
-        setCanceledOnTouchOutside(true)
+
         mirrorButton =
             requireViewById<TextView>(R.id.enable_display).apply {
                 setOnClickListener(onStartMirroringClickListener)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
new file mode 100644
index 0000000..8634b09
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 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.systemui.mediaprojection
+
+import android.media.projection.IMediaProjectionManager
+import android.os.Process
+import android.os.RemoteException
+import android.util.Log
+import com.android.internal.util.FrameworkStatsLog
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * Helper class for requesting that the server emit logs describing the MediaProjection setup
+ * experience.
+ */
+@SysUISingleton
+class MediaProjectionMetricsLogger
+@Inject
+constructor(private val service: IMediaProjectionManager) {
+    /**
+     * Request to log that the permission was requested.
+     *
+     * @param sessionCreationSource The entry point requesting permission to capture.
+     */
+    fun notifyPermissionProgress(state: Int, sessionCreationSource: Int) {
+        // TODO check that state & SessionCreationSource matches expected values
+        notifyToServer(state, sessionCreationSource)
+    }
+
+    /**
+     * Request to log that the permission request moved to the given state.
+     *
+     * Should not be used for the initialization state, since that
+     */
+    fun notifyPermissionProgress(state: Int) {
+        // TODO validate state is valid
+        notifyToServer(
+            state,
+            FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN)
+    }
+
+    /**
+     * Notifies system server that we are handling a particular state during the consent flow.
+     *
+     * Only used for emitting atoms.
+     *
+     * @param state The state that SystemUI is handling during the consent flow. Must be a valid
+     *   state defined in the MediaProjectionState enum.
+     * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
+     *   Indicates the entry point for requesting the permission. Must be a valid state defined in
+     *   the SessionCreationSource enum.
+     */
+    private fun notifyToServer(state: Int, sessionCreationSource: Int) {
+        Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource")
+        try {
+            service.notifyPermissionRequestStateChange(
+                Process.myUid(), state, sessionCreationSource)
+        } catch (e: RemoteException) {
+            Log.e(
+                TAG,
+                "Error notifying server of permission flow state $state from source $sessionCreationSource",
+                e)
+        }
+    }
+
+    companion object {
+        const val TAG = "MediaProjectionMetricsLogger"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ba0cf08..51972c2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -414,9 +414,7 @@
     private int mDisplayLeftInset = 0; // in pixels
 
     @VisibleForTesting
-    KeyguardClockPositionAlgorithm
-            mClockPositionAlgorithm =
-            new KeyguardClockPositionAlgorithm();
+    KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
     private final KeyguardClockPositionAlgorithm.Result
             mClockPositionResult =
             new KeyguardClockPositionAlgorithm.Result();
@@ -779,7 +777,8 @@
             KeyguardViewConfigurator keyguardViewConfigurator,
             KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
             SplitShadeStateController splitShadeStateController,
-            PowerInteractor powerInteractor) {
+            PowerInteractor powerInteractor,
+            KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm) {
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onKeyguardFadingAwayChanged() {
@@ -807,6 +806,7 @@
         mKeyguardInteractor = keyguardInteractor;
         mPowerInteractor = powerInteractor;
         mKeyguardViewConfigurator = keyguardViewConfigurator;
+        mClockPositionAlgorithm = keyguardClockPositionAlgorithm;
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index fb5a530..0a03af7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -26,14 +26,20 @@
 import com.android.app.animation.Interpolators;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.Logger;
+import com.android.systemui.log.dagger.KeyguardClockLog;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
 
+import javax.inject.Inject;
+
 /**
  * Utility class to calculate the clock position and top padding of notifications on Keyguard.
  */
 public class KeyguardClockPositionAlgorithm {
+    private static final String TAG = "KeyguardClockPositionAlgorithm";
 
     /**
      * Margin between the bottom of the status view and the notification shade.
@@ -147,6 +153,13 @@
      */
     private boolean mIsClockTopAligned;
 
+    private Logger mLogger;
+
+    @Inject
+    public KeyguardClockPositionAlgorithm(@KeyguardClockLog LogBuffer logBuffer) {
+        mLogger = new Logger(logBuffer, TAG);
+    }
+
     /**
      * Refreshes the dimension values.
      */
@@ -306,6 +319,20 @@
                 + fullyDarkBurnInOffset
                 + shift;
         mCurrentBurnInOffsetY = MathUtils.lerp(0, fullyDarkBurnInOffset, darkAmount);
+        final String inputs = "panelExpansion: " + panelExpansion + " darkAmount: " + darkAmount;
+        final String outputs = "clockY: " + clockY
+                + " burnInPreventionOffsetY: " + burnInPreventionOffsetY
+                + " fullyDarkBurnInOffset: " + fullyDarkBurnInOffset
+                + " shift: " + shift
+                + " mOverStretchAmount: " + mOverStretchAmount
+                + " mCurrentBurnInOffsetY: " + mCurrentBurnInOffsetY;
+        mLogger.i(msg -> {
+            return msg.getStr1() + " -> " + msg.getStr2();
+        }, msg -> {
+            msg.setStr1(inputs);
+            msg.setStr2(outputs);
+            return kotlin.Unit.INSTANCE;
+        });
         return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
new file mode 100644
index 0000000..85fd2af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 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.systemui.statusbar.phone
+
+import android.app.Dialog
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import com.android.systemui.res.R
+
+/** A dialog shown as a bottom sheet. */
+open class SystemUIBottomSheetDialog(
+    context: Context,
+    theme: Int = R.style.Theme_SystemUI_Dialog,
+) : Dialog(context, theme) {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        window?.apply {
+            setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+            addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+
+            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+            setGravity(Gravity.BOTTOM)
+            val edgeToEdgeHorizontally =
+                context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
+            if (edgeToEdgeHorizontally) {
+                decorView.setPadding(0, 0, 0, 0)
+                setLayout(
+                    WindowManager.LayoutParams.MATCH_PARENT,
+                    WindowManager.LayoutParams.WRAP_CONTENT
+                )
+
+                val lp = attributes
+                lp.fitInsetsSides = 0
+                lp.horizontalMargin = 0f
+                attributes = lp
+            }
+        }
+        setCanceledOnTouchOutside(true)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 8d8c70e..4f0cec5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -157,6 +157,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -328,6 +329,7 @@
     @Mock private CastController mCastController;
     @Mock private KeyguardRootView mKeyguardRootView;
     @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
+    @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;
 
     protected final int mMaxUdfpsBurnInOffsetY = 5;
     protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
@@ -667,7 +669,8 @@
                 mKeyguardViewConfigurator,
                 mKeyguardFaceAuthInteractor,
                 new ResourcesSplitShadeStateController(),
-                mPowerInteractor);
+                mPowerInteractor,
+                mKeyguardClockPositionAlgorithm);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
                 null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 03f5f00..3556703 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -30,9 +30,11 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.doze.util.BurnInHelperKt;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.FakeLogBuffer;
+import com.android.systemui.res.R;
 
 import org.junit.After;
 import org.junit.Before;
@@ -51,8 +53,7 @@
     private static final float OPAQUE = 1.f;
     private static final float TRANSPARENT = 0.f;
 
-    @Mock
-    private Resources mResources;
+    @Mock private Resources mResources;
 
     private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
     private KeyguardClockPositionAlgorithm.Result mClockPosition;
@@ -80,7 +81,8 @@
                 .mockStatic(BurnInHelperKt.class)
                 .startMocking();
 
-        mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm();
+        LogBuffer logBuffer = FakeLogBuffer.Factory.Companion.create();
+        mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(logBuffer);
         when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0);
         mClockPositionAlgorithm.loadDimens(mResources);
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 6dd32bc..e5225f6 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -182,7 +182,6 @@
         "android.hidl.manager-V1.2-java",
         "cbor-java",
         "display_flags_lib",
-        "dropbox_flags_lib",
         "icu4j_calendar_astronomer",
         "android.security.aaid_aidl-java",
         "netd-client",
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index f82a6aa..55069b7 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -16,14 +16,10 @@
 
 package com.android.server;
 
-import android.Manifest;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
-import android.app.compat.CompatChanges;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -34,7 +30,6 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.BundleMerger;
 import android.os.Debug;
@@ -71,7 +66,6 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.ObjectUtils;
 import com.android.server.DropBoxManagerInternal.EntrySource;
-import com.android.server.feature.flags.Flags;
 
 import libcore.io.IoUtils;
 
@@ -95,13 +89,6 @@
  * Clients use {@link DropBoxManager} to access this service.
  */
 public final class DropBoxManagerService extends SystemService {
-    /**
-     * For Android U and earlier versions, apps can continue to use the READ_LOGS permission,
-     * but for all subsequent versions, the READ_DROPBOX_DATA permission must be used.
-     */
-    @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    private static final long ENFORCE_READ_DROPBOX_DATA = 296060945L;
     private static final String TAG = "DropBoxManagerService";
     private static final int DEFAULT_AGE_SECONDS = 3 * 86400;
     private static final int DEFAULT_MAX_FILES = 1000;
@@ -122,6 +109,7 @@
     // Tags that we should drop by default.
     private static final List<String> DISABLED_BY_DEFAULT_TAGS =
             List.of("data_app_wtf", "system_app_wtf", "system_server_wtf");
+
     // TODO: This implementation currently uses one file per entry, which is
     // inefficient for smallish entries -- consider using a single queue file
     // per tag (or even globally) instead.
@@ -303,21 +291,8 @@
             if (!DropBoxManagerService.this.mBooted) {
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             }
-            if (Flags.enableReadDropboxPermission()) {
-                BroadcastOptions unbundledOptions = (options == null)
-                        ? BroadcastOptions.makeBasic() : BroadcastOptions.fromBundle(options);
-
-                unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true);
-                getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
-                        Manifest.permission.READ_DROPBOX_DATA, unbundledOptions.toBundle());
-
-                unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false);
-                getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
-                        Manifest.permission.READ_LOGS, unbundledOptions.toBundle());
-            } else {
-                getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
-                        android.Manifest.permission.READ_LOGS, options);
-            }
+            getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
+                    android.Manifest.permission.READ_LOGS, options);
         }
 
         private Intent createIntent(String tag, long time) {
@@ -597,16 +572,9 @@
             return true;
         }
 
-
-        String permission = Manifest.permission.READ_LOGS;
-        if (Flags.enableReadDropboxPermission()
-                && CompatChanges.isChangeEnabled(ENFORCE_READ_DROPBOX_DATA, callingUid)) {
-            permission = Manifest.permission.READ_DROPBOX_DATA;
-        }
-
         // Callers always need this permission
-        getContext().enforceCallingOrSelfPermission(permission, TAG);
-
+        getContext().enforceCallingOrSelfPermission(
+                android.Manifest.permission.READ_LOGS, TAG);
 
         // Callers also need the ability to read usage statistics
         switch (getContext().getSystemService(AppOpsManager.class).noteOp(
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 962f38f..beea063 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -214,9 +214,12 @@
     // external storage service.
     public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10;
 
-     /** Extended timeout for the system server watchdog. */
+    /** Extended timeout for the system server watchdog. */
     private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 60 * 1000;
 
+    /** Extended timeout for the system server watchdog for vold#partition operation. */
+    private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000;
+
     @GuardedBy("mLock")
     private final Set<Integer> mFuseMountedUser = new ArraySet<>();
 
@@ -2338,6 +2341,8 @@
         try {
             // TODO(b/135341433): Remove cautious logging when FUSE is stable
             Slog.i(TAG, "Mounting volume " + vol);
+            Watchdog.getInstance().setOneOffTimeoutForMonitors(
+                    SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow");
             mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
                 @Override
                 public boolean onVolumeChecking(FileDescriptor fd, String path,
@@ -2463,10 +2468,12 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
     @Override
     public void partitionPublic(String diskId) {
-
         super.partitionPublic_enforcePermission();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+        Watchdog.getInstance().setOneOffTimeoutForMonitors(
+                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
             waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2483,6 +2490,9 @@
         enforceAdminUser();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+        Watchdog.getInstance().setOneOffTimeoutForMonitors(
+                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
             waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2499,6 +2509,9 @@
         enforceAdminUser();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+        Watchdog.getInstance().setOneOffTimeoutForMonitors(
+                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
             waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS);
diff --git a/services/core/java/com/android/server/feature/Android.bp b/services/core/java/com/android/server/feature/Android.bp
deleted file mode 100644
index 067288d..0000000
--- a/services/core/java/com/android/server/feature/Android.bp
+++ /dev/null
@@ -1,12 +0,0 @@
-aconfig_declarations {
-    name: "dropbox_flags",
-    package: "com.android.server.feature.flags",
-    srcs: [
-        "dropbox_flags.aconfig",
-    ],
-}
-
-java_aconfig_library {
-    name: "dropbox_flags_lib",
-    aconfig_declarations: "dropbox_flags",
-}
diff --git a/services/core/java/com/android/server/feature/dropbox_flags.aconfig b/services/core/java/com/android/server/feature/dropbox_flags.aconfig
deleted file mode 100644
index fee4bf3..0000000
--- a/services/core/java/com/android/server/feature/dropbox_flags.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.server.feature.flags"
-
-flag{
-    name: "enable_read_dropbox_permission"
-    namespace: "preload_safety"
-    description: "Feature flag for permission to Read dropbox data"
-    bug: "287512663"
-}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 95ca08c..a158b18 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -97,20 +97,23 @@
 public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl {
 
     /**
-     * {@link MediaSession#setMediaButtonBroadcastReceiver(ComponentName)} throws an {@link
-     * IllegalArgumentException} if the provided {@link ComponentName} does not resolve to a valid
-     * {@link android.content.BroadcastReceiver broadcast receiver} for apps targeting Android U and
-     * above. For apps targeting Android T and below, the request will be ignored.
+     * {@link android.media.session.MediaSession#setMediaButtonBroadcastReceiver(
+     * android.content.ComponentName)} throws an {@link
+     * java.lang.IllegalArgumentException} if the provided {@link android.content.ComponentName}
+     * does not resolve to a valid {@link android.content.BroadcastReceiver broadcast receiver}
+     * for apps targeting Android U and above. For apps targeting Android T and below, the request
+     * will be ignored.
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     static final long THROW_FOR_INVALID_BROADCAST_RECEIVER = 270049379L;
 
     /**
-     * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} throws an {@link
-     * IllegalArgumentException} if the provided {@link PendingIntent} targets an {@link
-     * android.app.Activity activity} for apps targeting Android V and above. For apps targeting
-     * Android U and below, the request will be ignored.
+     * {@link android.media.session.MediaSession#setMediaButtonReceiver(android.app.PendingIntent)}
+     * throws an {@link java.lang.IllegalArgumentException} if the provided
+     * {@link android.app.PendingIntent} targets an {@link android.app.Activity activity} for
+     * apps targeting Android V and above. For apps targeting Android U and below, the request will
+     * be ignored.
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 13d1662..8cbc368 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -76,6 +76,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.Watchdog;
@@ -134,6 +135,7 @@
 
     private final MediaRouter mMediaRouter;
     private final MediaRouterCallback mMediaRouterCallback;
+    private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
     private MediaRouter.RouteInfo mMediaRouteInfo;
 
     @GuardedBy("mLock")
@@ -160,6 +162,7 @@
         mWmInternal = LocalServices.getService(WindowManagerInternal.class);
         mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
         mMediaRouterCallback = new MediaRouterCallback();
+        mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger();
         Watchdog.getInstance().addMonitor(this);
     }
 
@@ -193,6 +196,10 @@
         Looper createCallbackLooper() {
             return Looper.getMainLooper();
         }
+
+        MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
+            return MediaProjectionMetricsLogger.getInstance();
+        }
     }
 
     @Override
@@ -372,6 +379,10 @@
             if (mProjectionGrant != null) {
                 // Cache the session details.
                 mProjectionGrant.mSession = incomingSession;
+                mMediaProjectionMetricsLogger.notifyProjectionStateChange(
+                        mProjectionGrant.uid,
+                        FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
+                        FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
                 dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);
             }
             return true;
@@ -818,6 +829,19 @@
         }
 
         @Override // Binder call
+        @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+        public void notifyPermissionRequestStateChange(int hostUid, int state,
+                int sessionCreationSource) {
+            notifyPermissionRequestStateChange_enforcePermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mMediaProjectionMetricsLogger.notifyProjectionStateChange(hostUid, state, sessionCreationSource);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
         public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
             final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
new file mode 100644
index 0000000..f18ecad
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 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.server.media.projection;
+
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Class for emitting logs describing a MediaProjection session.
+ */
+public class MediaProjectionMetricsLogger {
+    private static MediaProjectionMetricsLogger sSingleton = null;
+
+    public static MediaProjectionMetricsLogger getInstance() {
+        if (sSingleton == null) {
+            sSingleton = new MediaProjectionMetricsLogger();
+        }
+        return sSingleton;
+    }
+
+    void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) {
+        write(hostUid, state, sessionCreationSource);
+    }
+
+    private void write(int hostUid, int state, int sessionCreationSource) {
+        FrameworkStatsLog.write(
+                /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED,
+                /* session_id */ 123,
+                /* state */ state,
+                /* previous_state */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN,
+                /* host_uid */ hostUid,
+                /* target_uid */ -1,
+                /* time_since_last_active */ 0,
+                /* creation_source */ sessionCreationSource);
+    }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 01ea33f..0718f2f 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2843,7 +2843,7 @@
         WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);
         WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
         boolean systemValid = systemWallpaper != null;
-        boolean lockValid = lockWallpaper != null && !isLockscreenLiveWallpaperEnabled();
+        boolean lockValid = lockWallpaper != null && isLockscreenLiveWallpaperEnabled();
         return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper}
                 : systemValid ? new WallpaperData[]{systemWallpaper}
                 : lockValid ? new WallpaperData[]{lockWallpaper}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index d85768d..f94aff7 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -26,6 +26,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -66,6 +67,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.testutils.OffsettableClock;
 import com.android.server.wm.WindowManagerInternal;
@@ -128,6 +130,14 @@
                 }
             };
 
+    private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector =
+            new MediaProjectionManagerService.Injector() {
+                @Override
+                MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
+                    return mMediaProjectionMetricsLogger;
+                }
+            };
+
     private Context mContext;
     private MediaProjectionManagerService mService;
     private OffsettableClock mClock;
@@ -142,6 +152,8 @@
     private PackageManager mPackageManager;
     @Mock
     private IMediaProjectionWatcherCallback mWatcherCallback;
+    @Mock
+    private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
     @Captor
     private ArgumentCaptor<ContentRecordingSession> mSessionCaptor;
 
@@ -734,6 +746,25 @@
     }
 
     @Test
+    public void setContentRecordingSession_success_logsCaptureInProgress()
+            throws Exception {
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+
+        service.setContentRecordingSession(DISPLAY_SESSION);
+
+        verify(mMediaProjectionMetricsLogger).notifyProjectionStateChange(
+                projection.uid,
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
+                FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN
+        );
+    }
+
+    @Test
     public void setContentRecordingSession_notifiesListenersOnCallbackLooper()
             throws Exception {
         mService = new MediaProjectionManagerService(mContext, mTestLooperInjector);
diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml
index 63acddf..0f47980 100644
--- a/tests/FlickerTests/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -61,9 +61,7 @@
         <option name="shell-timeout" value="6600s"/>
         <option name="test-timeout" value="6600s"/>
         <option name="hidden-api-checks" value="false"/>
-        <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed
         <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
-        -->
         <!-- PerfettoListener related arguments -->
         <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
         <option name="instrumentation-arg"