diff options
92 files changed, 1193 insertions, 305 deletions
diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline index 48c7657b1eb3..c28480e4f11a 100644 --- a/api/javadoc-lint-baseline +++ b/api/javadoc-lint-baseline @@ -142,8 +142,6 @@ android/media/AudioManager.java:287: lint: Unresolved link/see tag "android.medi 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/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag 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 9c7dc17f763b..20424beb2447 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -32392,7 +32392,7 @@ package android.os { 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 109d6b218c5f..cf3546057549 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 @@ public class DropBoxManager { /** * 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 @@ public class DropBoxManager { /** * 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 58376a77c705..0801dd8c0bd8 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.FileVisitor; 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 @@ public abstract class FileSystemProvider extends DocumentsProvider { 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 @@ public abstract class FileSystemProvider extends DocumentsProvider { } /** - * 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) + @Override + public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder) throws FileNotFoundException { - return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true); + return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ false); } + /** + * 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 Cursor queryChildDocuments( - String parentDocumentId, String[] projection, String sortOrder) - throws FileNotFoundException { - // Access to some directories is hidden for privacy reasons. - return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow); + public final Cursor queryChildDocumentsForManage(String documentId, String[] projection, + String sortOrder) throws FileNotFoundException { + return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true); } - private Cursor queryChildDocuments( - String parentDocumentId, String[] projection, String sortOrder, - @NonNull Predicate<File> filter) throws FileNotFoundException { - final File parent = getFileForDocId(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), parentDocumentId, parent); + resolveProjection(projection), documentId, parent); + + if (!parent.isDirectory()) { + Log.w(TAG, '"' + documentId + "\" is not a directory"); + return result; + } - if (!filter.test(parent)) { - Log.w(TAG, "No permission to access parentDocumentId: " + parentDocumentId); + if (!includeHidden && shouldHideDocument(documentId)) { + Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden"); 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"); + for (File file : FileUtils.listFilesOrEmpty(parent)) { + if (!includeHidden && shouldHideDocument(file)) continue; + + includeFile(result, null, file); } + return result; } @@ -452,23 +443,29 @@ public abstract class FileSystemProvider extends DocumentsProvider { * * @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 @@ public abstract class FileSystemProvider extends DocumentsProvider { 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 @@ public abstract class FileSystemProvider extends DocumentsProvider { return row; } - private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile( - "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$"); - /** - * In a scoped storage world, access to "Android/data" style directories are - * hidden for privacy reasons. + * 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 shouldHide(@NonNull File file) { - return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches()); + protected boolean shouldHideDocument(@NonNull String documentId) + throws FileNotFoundException { + return false; } - private boolean shouldShow(@NonNull File file) { - return !shouldHide(file); + /** + * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of + * a {@link String} {@code documentId}. + * + * @see #shouldHideDocument(String) + */ + protected final boolean shouldHideDocument(@NonNull File document) + throws FileNotFoundException { + return shouldHideDocument(getDocIdForFile(document)); } - 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 b5d70d379e0c..50253cf9e457 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -1315,7 +1315,16 @@ void AndroidRuntime::exit(int code) 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 e120f82347c1..ca768ad434f1 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 b13e9a248575..1df11369a049 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 8c635807022b..21690904fe42 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -463,7 +463,7 @@ public final class MediaRouter2 { /** * 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 6e2aaabf4b04..bbbe7f683b05 100644 --- a/media/java/android/media/midi/MidiUmpDeviceService.java +++ b/media/java/android/media/midi/MidiUmpDeviceService.java @@ -38,7 +38,7 @@ import java.util.List; * 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 d294601b44cc..80e22477efed 100644 --- a/media/java/android/media/projection/IMediaProjectionManager.aidl +++ b/media/java/android/media/projection/IMediaProjectionManager.aidl @@ -156,4 +156,24 @@ interface IMediaProjectionManager { + ".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 58bcd1d49c69..e71597a27a39 100644 --- a/omapi/aidl/Android.bp +++ b/omapi/aidl/Android.bp @@ -24,6 +24,11 @@ aidl_interface { 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 4c313b22f71e..3409c29d3c2c 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.List; 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 @@ public class ExternalStorageProvider extends FileSystemProvider { 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 @@ public class ExternalStorageProvider extends FileSystemProvider { 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; - } + 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; + } - // 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"); - } + // Allow all directories on USB, including the root. + if (isOnRemovableUsbStorage(documentId)) { + return false; + } - final String path = getPathFromDocId(docId); + // 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 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 /Download/ and /Android/ folders from the tree. + if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) || + equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) { + return true; + } - // Block /Android - if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(Locale.ROOT), - path.toLowerCase(Locale.ROOT))) { - 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; + } - // Block /Android/data, /Android/obb, /Android/sandbox and sub dirs - if (shouldHide(dir)) { - 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; - } catch (IOException e) { - throw new IllegalArgumentException( - "Failed to determine if " + docId + " should block from tree " + ": " + e); } + + 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 @@ public class ExternalStorageProvider extends FileSystemProvider { 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 @@ public class ExternalStorageProvider extends FileSystemProvider { 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 @@ public class ExternalStorageProvider extends FileSystemProvider { @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 @@ public class ExternalStorageProvider extends FileSystemProvider { } } + /** + * 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 @@ public class ExternalStorageProvider extends FileSystemProvider { } 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 18a8edc5e447..0144b6ea9040 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 @@ public class ExternalStorageProviderTest { 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 f65f5a3c9297..4f55e8b3bd4e 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -62,6 +62,7 @@ systemui_compose_java_defaults { 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 e4426fe97859..796abf4b52d6 100644 --- a/packages/SystemUI/compose/features/Android.bp +++ b/packages/SystemUI/compose/features/Android.bp @@ -33,6 +33,7 @@ android_library { 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 000000000000..050d1d5651ad --- /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 000000000000..81131bb689e4 --- /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 000000000000..33a59c2bcab3 --- /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 000000000000..f644a23ba0a3 --- /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 index 566967f920d3..041fc48dd09e 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 @@ fun SceneScope.animateSharedIntAsState( } /** + * 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 @@ fun SceneScope.animateSharedFloatAsState( } /** + * 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 @@ fun SceneScope.animateSharedDpAsState( } /** + * 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 @@ fun SceneScope.animateSharedColorAsState( 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 @@ internal fun <T> animateSharedValueAsState( 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 index 88944f10eab9..88944f10eab9 100644 --- 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 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 index ce96bbfc7976..ce96bbfc7976 100644 --- 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 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 index 98dbb67d7c66..98dbb67d7c66 100644 --- 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 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 index bc015eedb1b4..bc015eedb1b4 100644 --- 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 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 index 11bbf2aa987e..6dbeb69ff450 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 android.util.Log 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 @@ import androidx.compose.ui.unit.IntSize private const val TAG = "MovableElement" -private object MovableElementScopeImpl : MovableElementScope - @Composable internal fun MovableElement( layoutImpl: SceneTransitionLayoutImpl, @@ -51,6 +50,10 @@ internal fun MovableElement( // 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 @@ internal fun MovableElement( } } ) { - 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 @@ private fun shouldComposeMovableElement( 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 index ccdec6ea8c5e..ccdec6ea8c5e 100644 --- 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 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 index 3fd6828fca6b..3fd6828fca6b 100644 --- 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 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 index 4283c0e61df8..74e66d2a9949 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 @@ interface SceneScope { // 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 index 4952270cb5f2..4952270cb5f2 100644 --- 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 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 index 7a21211c3dde..7a21211c3dde 100644 --- 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 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 index 75dcb2e44c13..75dcb2e44c13 100644 --- 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 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 index 1cbfe3057ff0..1cbfe3057ff0 100644 --- 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 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 index 49669775fedd..49669775fedd 100644 --- 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 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 index f1c27178391c..f1c27178391c 100644 --- 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 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 index 95385d51cb25..95385d51cb25 100644 --- 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 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 index a1d63193bc73..a1d63193bc73 100644 --- 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 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 index 840800d838db..840800d838db 100644 --- 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 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 index 17032dc288e0..17032dc288e0 100644 --- 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 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 index 62d67f03f1d0..62d67f03f1d0 100644 --- 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 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 index 233ae597090b..233ae597090b 100644 --- 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 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 index 2ef8d56c6bc6..2ef8d56c6bc6 100644 --- 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 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 index 864b937a3fe0..864b937a3fe0 100644 --- 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 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 index 27f0948d5377..27f0948d5377 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt +++ b/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 index 135a6e4ec4e4..135a6e4ec4e4 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt +++ b/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 index cea8d9a65b43..cea8d9a65b43 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt +++ b/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 index 793a9a59405a..793a9a59405a 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt +++ b/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 index 741f00d9f19b..741f00d9f19b 100644 --- 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 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 index eb1a634ff491..eb1a634ff491 100644 --- 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 diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp new file mode 100644 index 000000000000..b53fae24865c --- /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 000000000000..1a9172ee20e0 --- /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 000000000000..7b7695eebd2f --- /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 index 4204cd5f0da0..4204cd5f0da0 100644 --- 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 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 index 04b3f8a1dfe7..04b3f8a1dfe7 100644 --- 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 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 index 5afd420a5e16..5afd420a5e16 100644 --- 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 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 index df3b72aa5533..df3b72aa5533 100644 --- 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 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 index e0ae1be69aaf..e0ae1be69aaf 100644 --- 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 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 index 83572620c88a..b4c393e9bfbe 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 @@ object TestElements { /** 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 index fa94b25028a2..fa94b25028a2 100644 --- 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 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 index 8ef6757d33bd..8ef6757d33bd 100644 --- 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 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 index d1205e727cf9..d1205e727cf9 100644 --- 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 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 index 2a27763f1d5c..2a27763f1d5c 100644 --- 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 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 index 384355ca951f..384355ca951f 100644 --- 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 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 index 2af363860272..e94eff32c30c 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.TestElements 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 index 1d559fd6bd8a..1d559fd6bd8a 100644 --- 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 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 index 03d231a7fcc6..03d231a7fcc6 100644 --- 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 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 index 8e2b77a2f2a0..8e2b77a2f2a0 100644 --- 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 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 index d6f64bfe4974..d6f64bfe4974 100644 --- 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 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 index fbd1b512c50a..fbd1b512c50a 100644 --- 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 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 index bf7bf98878e6..bf7bf98878e6 100644 --- 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 diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml index a51c55ee965f..8cfcb689eced 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 ea3c012afc43..1f671ac4c875 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 1add90ff4083..6856717653bd 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 5a83c7d2dc2a..6377df3410ab 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 04a9cae31382..d57f31f91df1 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.hardware.input.InputManager; 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 @@ public class FrameworkServicesModule { } @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 7510cf6ce04c..d19efbdd8026 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 @@ class MirroringConfirmationDialog( 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 @@ class MirroringConfirmationDialog( 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 000000000000..8634b0911391 --- /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 ba0cf08150f6..51972c2d025c 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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 fb5a530e3875..0a03af7d9387 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 android.util.MathUtils; 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 @@ public class KeyguardClockPositionAlgorithm { */ 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 @@ public class KeyguardClockPositionAlgorithm { + 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 000000000000..85fd2afed9ec --- /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 8d8c70e26ab2..4f0cec5759e5 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.HeadsUpTouchHelper; 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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { 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 03f5f005ee47..3556703a2fa8 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 android.testing.AndroidTestingRunner; 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 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { 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 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { .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 6dd32bc2c9bb..e5225f65f22a 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -182,7 +182,6 @@ java_library_static { "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 f82a6aabfefb..55069b779a37 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.content.res.Resources; 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.DumpUtils; 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 @@ import java.util.zip.GZIPOutputStream; * 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 @@ public final class DropBoxManagerService extends SystemService { // 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 @@ public final class DropBoxManagerService extends SystemService { 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 @@ public final class DropBoxManagerService extends SystemService { 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 962f38f10b5d..beea063221fb 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -214,9 +214,12 @@ class StorageManagerService extends IStorageManager.Stub // 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 @@ class StorageManagerService extends IStorageManager.Stub 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 @@ class StorageManagerService extends IStorageManager.Stub @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 @@ class StorageManagerService extends IStorageManager.Stub 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 @@ class StorageManagerService extends IStorageManager.Stub 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 067288d6650d..000000000000 --- 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 fee4bf377ddc..000000000000 --- 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 95ca08cc7fe9..a158b18d91b4 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 @@ import java.util.concurrent.CopyOnWriteArrayList; 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 13d166294603..8cbc368467bb 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.GuardedBy; 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 @@ public final class MediaProjectionManagerService extends SystemService private final MediaRouter mMediaRouter; private final MediaRouterCallback mMediaRouterCallback; + private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; private MediaRouter.RouteInfo mMediaRouteInfo; @GuardedBy("mLock") @@ -160,6 +162,7 @@ public final class MediaProjectionManagerService extends SystemService 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 @@ public final class MediaProjectionManagerService extends SystemService Looper createCallbackLooper() { return Looper.getMainLooper(); } + + MediaProjectionMetricsLogger mediaProjectionMetricsLogger() { + return MediaProjectionMetricsLogger.getInstance(); + } } @Override @@ -372,6 +379,10 @@ public final class MediaProjectionManagerService extends SystemService 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 @@ public final class MediaProjectionManagerService extends SystemService } @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 000000000000..f18ecad09c42 --- /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 01ea33f1aecd..0718f2f284a5 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 @@ public class WallpaperManagerService extends IWallpaperManager.Stub 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 d85768dd7588..f94aff706a67 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.media.projection.ReviewGrantedConsentResult.UNKNOWN; 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.FlakyTest; 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 @@ public class MediaProjectionManagerServiceTest { } }; + 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 @@ public class MediaProjectionManagerServiceTest { private PackageManager mPackageManager; @Mock private IMediaProjectionWatcherCallback mWatcherCallback; + @Mock + private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; @Captor private ArgumentCaptor<ContentRecordingSession> mSessionCaptor; @@ -734,6 +746,25 @@ public class MediaProjectionManagerServiceTest { } @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 63acddf25e02..0f4798090da8 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" |