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"