Snap for 11296156 from 89c99dea0f83b620e332d719847fcfe803d6a2a1 to mainline-tzdata5-release

Change-Id: I3b5449c4f52850d6f33dda5f717c3bc8a2732373
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8884bd9..6bf7182 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -24,7 +24,7 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.USE_RESERVED_DISK" />
+    <uses-permission android:name="android.permission.USE_RESERVED_DISK" android:maxSdkVersion="34" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS" />
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
diff --git a/apex/framework/api/current.txt b/apex/framework/api/current.txt
index cddfcfa..d760d14 100644
--- a/apex/framework/api/current.txt
+++ b/apex/framework/api/current.txt
@@ -151,6 +151,7 @@
     field public static final String EXTRA_MEDIA_TITLE = "android.intent.extra.title";
     field public static final String EXTRA_OUTPUT = "output";
     field @FlaggedApi("com.android.providers.media.flags.pick_ordered_images") public static final String EXTRA_PICK_IMAGES_IN_ORDER = "android.provider.extra.PICK_IMAGES_IN_ORDER";
+    field @FlaggedApi("com.android.providers.media.flags.picker_default_tab") public static final String EXTRA_PICK_IMAGES_LAUNCH_TAB = "android.provider.extra.PICK_IMAGES_LAUNCH_TAB";
     field public static final String EXTRA_PICK_IMAGES_MAX = "android.provider.extra.PICK_IMAGES_MAX";
     field public static final String EXTRA_SCREEN_ORIENTATION = "android.intent.extra.screenOrientation";
     field public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons";
@@ -172,6 +173,8 @@
     field public static final String MEDIA_SCANNER_VOLUME = "volume";
     field public static final String META_DATA_REVIEW_GALLERY_PREWARM_SERVICE = "android.media.review_gallery_prewarm_service";
     field public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE = "android.media.still_image_camera_preview_service";
+    field @FlaggedApi("com.android.providers.media.flags.picker_default_tab") public static final int PICK_IMAGES_TAB_ALBUMS = 0; // 0x0
+    field @FlaggedApi("com.android.providers.media.flags.picker_default_tab") public static final int PICK_IMAGES_TAB_IMAGES = 1; // 0x1
     field public static final String QUERY_ARG_INCLUDE_RECENTLY_UNMOUNTED_VOLUMES = "android:query-arg-recently-unmounted-volumes";
     field public static final String QUERY_ARG_MATCH_FAVORITE = "android:query-arg-match-favorite";
     field public static final String QUERY_ARG_MATCH_PENDING = "android:query-arg-match-pending";
diff --git a/apex/framework/java/android/provider/AsyncContentProvider.java b/apex/framework/java/android/provider/AsyncContentProvider.java
index 25d5609..e12a0f6 100644
--- a/apex/framework/java/android/provider/AsyncContentProvider.java
+++ b/apex/framework/java/android/provider/AsyncContentProvider.java
@@ -37,7 +37,7 @@
  */
 public final class AsyncContentProvider {
 
-    private static final long TIMEOUT_IN_SECONDS = 5L;
+    private static final long TIMEOUT_IN_MINUTES = 3L;
 
     private final IAsyncContentProvider mAsyncContentProvider;
 
@@ -53,7 +53,7 @@
         CompletableFuture<ParcelFileDescriptor> future = new CompletableFuture<>();
         RemoteCallback callback = new RemoteCallback(result -> setResult(result, future));
         mAsyncContentProvider.openMedia(mediaId, callback);
-        return future.get(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        return future.get(TIMEOUT_IN_MINUTES, TimeUnit.MINUTES);
     }
 
     private void setResult(Bundle result, CompletableFuture<ParcelFileDescriptor> future) {
@@ -71,4 +71,4 @@
                                     + "CloudMediaProvider"));
         }
     }
-}
\ No newline at end of file
+}
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index b1172af..d232fd3 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -872,6 +872,41 @@
     }
 
     /**
+     * The name of an optional intent-extra used to allow apps to specify the tab the picker should
+     * open with. The extra can only be specified in {@link MediaStore#ACTION_PICK_IMAGES}.
+     * <p>
+     * The value of this intent-extra must be one of: {@link MediaStore#PICK_IMAGES_TAB_ALBUMS}
+     * for the albums tab and {@link MediaStore#PICK_IMAGES_TAB_IMAGES} for the photos tab.
+     * The system will decide which tab to open by default and in most cases,
+     * it is {@link MediaStore#PICK_IMAGES_TAB_IMAGES} i.e. the photos tab.
+     */
+    @FlaggedApi("com.android.providers.media.flags.picker_default_tab")
+    public static final String EXTRA_PICK_IMAGES_LAUNCH_TAB =
+            "android.provider.extra.PICK_IMAGES_LAUNCH_TAB";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "PICK_IMAGES_TAB_" }, value = {
+            PICK_IMAGES_TAB_ALBUMS,
+            PICK_IMAGES_TAB_IMAGES
+    })
+    public @interface PickImagesTab { }
+
+    /**
+     * One of the permitted values for {@link MediaStore#EXTRA_PICK_IMAGES_LAUNCH_TAB} to open the
+     * picker with albums tab.
+     */
+    @FlaggedApi("com.android.providers.media.flags.picker_default_tab")
+    public static final int PICK_IMAGES_TAB_ALBUMS = 0;
+
+    /**
+     * One of the permitted values for {@link MediaStore#EXTRA_PICK_IMAGES_LAUNCH_TAB} to open the
+     * picker with photos tab.
+     */
+    @FlaggedApi("com.android.providers.media.flags.picker_default_tab")
+    public static final int PICK_IMAGES_TAB_IMAGES = 1;
+
+    /**
      * Specify that the caller wants to receive the original media format without transcoding.
      *
      * <b>Caution: using this flag can cause app
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 131c795..6398796 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -72,7 +72,7 @@
     <string name="picker_profile_work_paused_title" msgid="382212880704235925">"Applis professionnelles en pause"</string>
     <string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Pour ouvrir des photos professionnelles, activez vos applis professionnelles, puis réessayez"</string>
     <string name="picker_privacy_message" msgid="9132700451027116817">"Cette appli peut uniquement accéder aux photos que vous sélectionnez"</string>
-    <string name="picker_header_permissions" msgid="675872774407768495">"Sélectionnez les photos et videos auxquelles cette appli peut accéder"</string>
+    <string name="picker_header_permissions" msgid="675872774407768495">"Sélectionnez les photos et vidéos auxquelles cette appli peut accéder"</string>
     <string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> élément}one{<xliff:g id="COUNT_1">^1</xliff:g> élément}many{<xliff:g id="COUNT_1">^1</xliff:g> éléments}other{<xliff:g id="COUNT_1">^1</xliff:g> éléments}}"</string>
     <string name="picker_add_button_multi_select" msgid="4005164092275518399">"Ajouter (<xliff:g id="COUNT">^1</xliff:g>)"</string>
     <string name="picker_add_button_multi_select_permissions" msgid="5138751105800138838">"Autoriser (<xliff:g id="COUNT">^1</xliff:g>)"</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 55a1f9d..351fd0b 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -148,6 +148,7 @@
 
     <style name="SelectedMediaPreloaderDialogTheme"
            parent="@style/ThemeOverlay.MaterialComponents.MaterialAlertDialog.Centered">
+        <item name="android:background">@color/picker_background_color</item>
         <item name="android:textColor">?attr/colorOnSurfaceVariant</item>
         <item name="materialAlertDialogTitleTextStyle">@style/AlertDialogTitleStyle</item>
     </style>
diff --git a/src/com/android/providers/media/AccessChecker.java b/src/com/android/providers/media/AccessChecker.java
index adde626..986b444 100644
--- a/src/com/android/providers/media/AccessChecker.java
+++ b/src/com/android/providers/media/AccessChecker.java
@@ -363,6 +363,16 @@
         return PACKAGE_USER_ID_COLUMN + "=" + callingIdentity.uid / MediaStore.PER_USER_RANGE;
     }
 
+    /**
+     * Returns true if redaction is needed for openFile calls on picker uri by checking calling
+     * package permission
+     *
+     * @param callingIdentity - the current caller
+     */
+    public static boolean isRedactionNeededForPickerUri(LocalCallingIdentity callingIdentity) {
+        return callingIdentity.hasPermission(LocalCallingIdentity.PERMISSION_IS_REDACTION_NEEDED);
+    }
+
     @VisibleForTesting
     static String getWhereForMediaTypeMatch(int mediaType) {
         return bindSelection("media_type=?", mediaType);
diff --git a/src/com/android/providers/media/DatabaseBackupAndRecovery.java b/src/com/android/providers/media/DatabaseBackupAndRecovery.java
index 2a99e7d..7a601eb 100644
--- a/src/com/android/providers/media/DatabaseBackupAndRecovery.java
+++ b/src/com/android/providers/media/DatabaseBackupAndRecovery.java
@@ -155,6 +155,21 @@
 
     private static Map<String, String> sOwnerIdRelationMap;
 
+    public static final String STABLE_URI_INTERNAL_PROPERTY =
+            "persist.sys.fuse.backup.internal_db_backup";
+
+    private static boolean STABLE_URI_INTERNAL_PROPERTY_VALUE = true;
+
+    public static final String STABLE_URI_EXTERNAL_PROPERTY =
+            "persist.sys.fuse.backup.external_volume_backup";
+
+    private static boolean STABLE_URI_EXTERNAL_PROPERTY_VALUE = false;
+
+    public static final String STABLE_URI_PUBLIC_PROPERTY =
+            "persist.sys.fuse.backup.public_db_backup";
+
+    private static boolean STABLE_URI_PUBLIC_PROPERTY_VALUE = false;
+
     protected DatabaseBackupAndRecovery(ConfigStore configStore, VolumeCache volumeCache) {
         mConfigStore = configStore;
         mVolumeCache = volumeCache;
@@ -167,19 +182,18 @@
         switch (volumeName) {
             case MediaStore.VOLUME_INTERNAL:
                 return mConfigStore.isStableUrisForInternalVolumeEnabled()
-                        || SystemProperties.getBoolean("persist.sys.fuse.backup.internal_db_backup",
-                        /* defaultValue */ false);
+                        || SystemProperties.getBoolean(STABLE_URI_INTERNAL_PROPERTY,
+                        /* defaultValue */ STABLE_URI_INTERNAL_PROPERTY_VALUE);
             case MediaStore.VOLUME_EXTERNAL_PRIMARY:
                 return mConfigStore.isStableUrisForExternalVolumeEnabled()
-                        || SystemProperties.getBoolean(
-                        "persist.sys.fuse.backup.external_volume_backup",
-                        /* defaultValue */ false);
+                        || SystemProperties.getBoolean(STABLE_URI_EXTERNAL_PROPERTY,
+                        /* defaultValue */ STABLE_URI_EXTERNAL_PROPERTY_VALUE);
             default:
                 // public volume
                 return isStableUrisEnabled(MediaStore.VOLUME_EXTERNAL_PRIMARY)
                         && mConfigStore.isStableUrisForPublicVolumeEnabled()
-                        || SystemProperties.getBoolean("persist.sys.fuse.backup.public_db_backup",
-                        /* defaultValue */ false);
+                        || SystemProperties.getBoolean(STABLE_URI_PUBLIC_PROPERTY,
+                        /* defaultValue */ STABLE_URI_PUBLIC_PROPERTY_VALUE);
         }
     }
 
@@ -215,15 +229,18 @@
             FuseDaemon fuseDaemon = getFuseDaemonForFileWithWait(new File(
                     DatabaseBackupAndRecovery.EXTERNAL_PRIMARY_ROOT_PATH));
             Log.d(TAG, "Received db backup Fuse Daemon for: " + volumeName);
-            if (isStableUrisEnabled(volumeName)) {
-                if (MediaStore.VOLUME_EXTERNAL_PRIMARY.equalsIgnoreCase(volumeName)) {
-                    // Setup internal and external volumes
-                    fuseDaemon.setupVolumeDbBackup();
-                } else {
-                    // Setup public volume
-                    fuseDaemon.setupPublicVolumeDbBackup(volumeName);
-                }
+            if (MediaStore.VOLUME_EXTERNAL_PRIMARY.equalsIgnoreCase(volumeName) && (
+                    isStableUrisEnabled(MediaStore.VOLUME_INTERNAL) || isStableUrisEnabled(
+                            MediaStore.VOLUME_EXTERNAL_PRIMARY))) {
+                // Setup internal and external volumes
+                fuseDaemon.setupVolumeDbBackup();
                 mSetupCompletePublicVolumes.add(volumeName);
+            } else if (isStableUrisEnabled(volumeName)) {
+                // Setup public volume
+                fuseDaemon.setupPublicVolumeDbBackup(volumeName);
+                mSetupCompletePublicVolumes.add(volumeName);
+            } else {
+                return;
             }
         } catch (IOException e) {
             Log.e(TAG, "Failure in setting up backup and recovery for volume: " + volumeName, e);
@@ -241,7 +258,7 @@
     public void backupDatabases(DatabaseHelper internalDatabaseHelper,
             DatabaseHelper externalDatabaseHelper, CancellationSignal signal) {
         setupVolumeDbBackupAndRecovery(MediaStore.VOLUME_EXTERNAL_PRIMARY,
-          new File(EXTERNAL_PRIMARY_ROOT_PATH));
+                new File(EXTERNAL_PRIMARY_ROOT_PATH));
         Log.i(TAG, "Triggering database backup");
         backupInternalDatabase(internalDatabaseHelper, signal);
         backupExternalDatabase(externalDatabaseHelper, MediaStore.VOLUME_EXTERNAL_PRIMARY, signal);
@@ -283,6 +300,8 @@
         }
 
         if (!mSetupCompletePublicVolumes.contains(MediaStore.VOLUME_EXTERNAL_PRIMARY)) {
+            Log.w(TAG,
+                "Setup is not present for backup of internal and external primary volume.");
             return;
         }
 
diff --git a/src/com/android/providers/media/LocalUriMatcher.java b/src/com/android/providers/media/LocalUriMatcher.java
index 888a619..9beebdb 100644
--- a/src/com/android/providers/media/LocalUriMatcher.java
+++ b/src/com/android/providers/media/LocalUriMatcher.java
@@ -72,11 +72,12 @@
     static final int DOWNLOADS_ID = 801;
 
     static final int PICKER = 900;
-    static final int PICKER_ID = 901;
+    public static final int PICKER_ID = 901;
     static final int PICKER_INTERNAL_MEDIA_ALL = 902;
     static final int PICKER_INTERNAL_MEDIA_LOCAL = 903;
     static final int PICKER_INTERNAL_ALBUMS_ALL = 904;
     static final int PICKER_INTERNAL_ALBUMS_LOCAL = 905;
+    public static final int PICKER_GET_CONTENT_ID = 906;
 
     public static final int MEDIA_GRANTS = 1000;
 
@@ -121,6 +122,11 @@
         // content://media/picker/<user-id>/<authority>/media/<media-id>
         mPublic.addURI(auth, "picker/#/*/media/*", PICKER_ID);
 
+        // content://media/picker_get_content/<user-id>/<media-id>
+        mPublic.addURI(auth, "picker_get_content/#/#", PICKER_GET_CONTENT_ID);
+        // content://media/picker_get_content/<user-id>/<authority>/media/<media-id>
+        mPublic.addURI(auth, "picker_get_content/#/*/media/*", PICKER_GET_CONTENT_ID);
+
         mPublic.addURI(auth, "cli", CLI);
 
         mPublic.addURI(auth, "*/images/media", IMAGES_MEDIA);
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 32a8e70..ec2d8c2 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -61,6 +61,7 @@
 import static com.android.providers.media.AccessChecker.getWhereForUserSelectedAccess;
 import static com.android.providers.media.AccessChecker.hasAccessToCollection;
 import static com.android.providers.media.AccessChecker.hasUserSelectedAccess;
+import static com.android.providers.media.AccessChecker.isRedactionNeededForPickerUri;
 import static com.android.providers.media.DatabaseHelper.EXTERNAL_DATABASE_NAME;
 import static com.android.providers.media.DatabaseHelper.INTERNAL_DATABASE_NAME;
 import static com.android.providers.media.LocalCallingIdentity.APPOP_REQUEST_INSTALL_PACKAGES_FOR_SHARED_UID;
@@ -109,6 +110,7 @@
 import static com.android.providers.media.LocalUriMatcher.IMAGES_THUMBNAILS_ID;
 import static com.android.providers.media.LocalUriMatcher.MEDIA_GRANTS;
 import static com.android.providers.media.LocalUriMatcher.MEDIA_SCANNER;
+import static com.android.providers.media.LocalUriMatcher.PICKER_GET_CONTENT_ID;
 import static com.android.providers.media.LocalUriMatcher.PICKER_ID;
 import static com.android.providers.media.LocalUriMatcher.PICKER_INTERNAL_ALBUMS_ALL;
 import static com.android.providers.media.LocalUriMatcher.PICKER_INTERNAL_ALBUMS_LOCAL;
@@ -122,6 +124,8 @@
 import static com.android.providers.media.LocalUriMatcher.VIDEO_THUMBNAILS_ID;
 import static com.android.providers.media.LocalUriMatcher.VOLUMES;
 import static com.android.providers.media.LocalUriMatcher.VOLUMES_ID;
+import static com.android.providers.media.PickerUriResolver.PICKER_GET_CONTENT_SEGMENT;
+import static com.android.providers.media.PickerUriResolver.PICKER_SEGMENT;
 import static com.android.providers.media.PickerUriResolver.getMediaUri;
 import static com.android.providers.media.photopicker.data.ItemsProvider.EXTRA_MIME_TYPE_SELECTION;
 import static com.android.providers.media.scan.MediaScanner.REASON_DEMAND;
@@ -1340,7 +1344,8 @@
                 mConfigStore, pickerSyncLockManager);
         mPickerDataLayer = PickerDataLayer.create(context, mPickerDbFacade, mPickerSyncController,
                 mConfigStore);
-        mPickerUriResolver = new PickerUriResolver(context, mPickerDbFacade, mProjectionHelper);
+        mPickerUriResolver = new PickerUriResolver(context, mPickerDbFacade, mProjectionHelper,
+                mUriMatcher);
 
         if (SdkLevel.isAtLeastS()) {
             mTranscodeHelper = new TranscodeHelperImpl(context, this, mConfigStore);
@@ -2275,7 +2280,8 @@
     @Keep
     public FileLookupResult onFileLookupForFuse(String path, int uid, int tid) {
         uid = getBinderUidForFuse(uid, tid);
-        final int userId = uidToUserId(uid);
+        // Use MediaProviders UserId as the caller might be calling cross profile.
+        final int userId = UserHandle.myUserId();
 
         if (isSyntheticPath(path, userId)) {
             if (isRedactedPath(path, userId)) {
@@ -2362,13 +2368,14 @@
         boolean result = false;
         switch (segmentCount) {
             case 1:
-                // .../picker
-                if (lastSegment.equals("picker")) {
+                // .../picker or .../picker_get_content
+                if (lastSegment.equals(PICKER_SEGMENT) || lastSegment.equals(
+                        PICKER_GET_CONTENT_SEGMENT)) {
                     result = file.exists() || file.mkdir();
                 }
                 break;
             case 2:
-                // .../picker/<user-id>
+                // .../picker/<user-id> or .../picker_get_content/<user-id>
                 try {
                     Integer.parseInt(lastSegment);
                     result = file.exists() || file.mkdir();
@@ -2378,21 +2385,24 @@
                 }
                 break;
             case 3:
-                // .../picker/<user-id>/<authority>
+                // .../picker/<user-id>/<authority> or .../picker_get_content/<user-id>/<authority>
                 result = preparePickerAuthorityPathSegment(file, lastSegment, uid);
                 break;
             case 4:
-                // .../picker/<user-id>/<authority>/media
+                // .../picker/<user-id>/<authority>/media or
+                // .../picker_get_content/<user-id>/<authority>/media
                 if (lastSegment.equals("media")) {
                     result = file.exists() || file.mkdir();
                 }
                 break;
             case 5:
-                // .../picker/<user-id>/<authority>/media/<media-id.extension>
+                // .../picker/<user-id>/<authority>/media/<media-id.extension> or
+                // .../picker_get_content/<user-id>/<authority>/media/<media-id.extension>
+                final String pickerSegmentType = syntheticRelativePathSegments.get(0);
                 final String fileUserId = syntheticRelativePathSegments.get(1);
                 final String authority = syntheticRelativePathSegments.get(2);
-                result = preparePickerMediaIdPathSegment(file, authority, lastSegment, fileUserId,
-                        uid);
+                result = preparePickerMediaIdPathSegment(file, pickerSegmentType, authority,
+                        lastSegment, fileUserId, uid);
                 break;
         }
 
@@ -2410,8 +2420,9 @@
                     new long[0]);
         }
 
-        // ['', 'storage', 'emulated', '0', 'transforms', 'synthetic', 'picker', '<user-id>',
-        // '<host>', 'media', '<fileName>']
+        // ['', 'storage', 'emulated', '0', 'transforms', 'synthetic',
+        // 'picker' or 'picker_get_content', '<user-id>', '<host>', 'media', '<fileName>']
+        final String pickerSegmentType = segments[6];
         final String userId = segments[7];
         final String fileName = segments[10];
         final String host = segments[8];
@@ -2447,7 +2458,19 @@
 
         try (FileInputStream fis = new FileInputStream(pfd.getFileDescriptor())) {
             final String mimeType = MimeUtils.resolveMimeType(new File(path));
-            final long[] redactionRanges = getRedactionRanges(fis, mimeType).redactionRanges;
+            // Picker segment indicates we need to force redact location metadata.
+            // Picker_get_content indicates that we need to check A_M_L permission to decide if the
+            // metadata needs to be redacted
+            LocalCallingIdentity callingIdentityForOriginalUid = getCachedCallingIdentityForFuse(
+                    uid);
+            final boolean isRedactionNeeded = pickerSegmentType.equalsIgnoreCase(PICKER_SEGMENT)
+                    || callingIdentityForOriginalUid == null
+                    || isRedactionNeededForPickerUri(callingIdentityForOriginalUid);
+            Log.v(TAG, "Redaction needed for file open: " + isRedactionNeeded);
+            long[] redactionRanges = new long[0];
+            if (isRedactionNeeded) {
+                redactionRanges = getRedactionRanges(fis, mimeType).redactionRanges;
+            }
             return new FileOpenResult(0 /* status */, uid, /* transformsUid */ 0,
                     /* nativeFd */ pfd.detachFd(), redactionRanges);
         } catch (IOException e) {
@@ -2464,13 +2487,14 @@
         return false;
     }
 
-    private boolean preparePickerMediaIdPathSegment(File file, String authority, String fileName,
-            String userId, int uid) {
+    private boolean preparePickerMediaIdPathSegment(File file, String pickerSegmentType,
+            String authority, String fileName, String userId, int uid) {
         final String mediaId = extractFileName(fileName);
-        final String[] projection = new String[] { MediaStore.PickerMediaColumns.SIZE };
+        final String[] projection = new String[]{MediaStore.PickerMediaColumns.SIZE};
 
-        final Uri uri = Uri.parse("content://media/picker/" + userId + "/" + authority + "/media/"
-                + mediaId);
+        final Uri uri = Uri.parse(
+                "content://media/" + pickerSegmentType + "/" + userId + "/" + authority + "/media/"
+                        + mediaId);
         try (Cursor cursor = mPickerUriResolver.query(uri, projection, /* callingPid */0, uid,
                 mCallingIdentity.get().getPackageName())) {
             if (cursor != null && cursor.moveToFirst()) {
@@ -4055,6 +4079,7 @@
                 return Downloads.CONTENT_TYPE;
 
             case PICKER_ID:
+            case PICKER_GET_CONTENT_ID:
                 return mPickerUriResolver.getType(url, Binder.getCallingPid(),
                         Binder.getCallingUid());
         }
@@ -8651,7 +8676,7 @@
 
     private boolean isPickerUri(Uri uri) {
         final int match = matchUri(uri, /* allowHidden */ isCallingPackageAllowedHidden());
-        return match == PICKER_ID;
+        return match == PICKER_ID || match == PICKER_GET_CONTENT_ID;
     }
 
     @Override
@@ -8678,9 +8703,20 @@
         uri = safeUncanonicalize(uri);
 
         if (isPickerUri(uri)) {
-            final int callingPid = mCallingIdentity.get().pid;
-            final int callingUid = mCallingIdentity.get().uid;
-            return mPickerUriResolver.openFile(uri, mode, signal, callingPid, callingUid);
+            int tid = Process.myTid();
+            synchronized (mPendingOpenInfo) {
+                mPendingOpenInfo.put(tid, new PendingOpenInfo(
+                        Binder.getCallingUid(), /* mediaCapabilitiesUid */ 0, /* shouldRedact */
+                        false, /* transcodeReason */ 0));
+            }
+
+            try {
+                return mPickerUriResolver.openFile(uri, mode, signal, mCallingIdentity.get());
+            } finally {
+                synchronized (mPendingOpenInfo) {
+                    mPendingOpenInfo.remove(tid);
+                }
+            }
         }
 
         final boolean allowHidden = isCallingPackageAllowedHidden();
@@ -8813,10 +8849,21 @@
 
         // This is needed for thumbnail resolution as it doesn't go through openFileCommon
         if (isPickerUri(uri)) {
-            final int callingPid = mCallingIdentity.get().pid;
-            final int callingUid = mCallingIdentity.get().uid;
-            return mPickerUriResolver.openTypedAssetFile(uri, mimeTypeFilter, opts, signal,
-                    callingPid, callingUid);
+            int tid = Process.myTid();
+            synchronized (mPendingOpenInfo) {
+                mPendingOpenInfo.put(tid, new PendingOpenInfo(
+                        Binder.getCallingUid(), /* mediaCapabilitiesUid */ 0, /* shouldRedact */
+                        false, /* transcodeReason */ 0));
+            }
+
+            try {
+                return mPickerUriResolver.openTypedAssetFile(uri, mimeTypeFilter, opts, signal,
+                        mCallingIdentity.get());
+            } finally {
+                synchronized (mPendingOpenInfo) {
+                    mPendingOpenInfo.remove(tid);
+                }
+            }
         }
 
         // TODO: enforce that caller has access to this uri
@@ -9862,7 +9909,8 @@
         boolean isSuccess = false;
 
         final int originalUid = getBinderUidForFuse(uid, tid);
-        final int callingUserId = uidToUserId(uid);
+        // Use MediaProvider's own ID here since the caller may be cross profile.
+        final int userId = UserHandle.myUserId();
         int mediaCapabilitiesUid = 0;
         final PendingOpenInfo pendingOpenInfo;
         synchronized (mPendingOpenInfo) {
@@ -9876,14 +9924,14 @@
         try {
             boolean forceRedaction = false;
             String redactedUriId = null;
-            if (isSyntheticPath(path, callingUserId)) {
+            if (isSyntheticPath(path, userId)) {
                 if (forWrite) {
                     // Synthetic URIs are not allowed to update EXIF headers.
                     return new FileOpenResult(OsConstants.EACCES /* status */, originalUid,
                             mediaCapabilitiesUid, new long[0]);
                 }
 
-                if (isRedactedPath(path, callingUserId)) {
+                if (isRedactedPath(path, userId)) {
                     redactedUriId = extractFileName(path);
 
                     // If path is redacted Uris' path, ioPath must be the real path, ioPath must
@@ -9893,7 +9941,7 @@
                     // Irrespective of the permissions we want to redact in this case.
                     redact = true;
                     forceRedaction = true;
-                } else if (isPickerPath(path, callingUserId)) {
+                } else if (isPickerPath(path, userId)) {
                     return handlePickerFileOpen(path, originalUid);
                 } else {
                     // we don't support any other transformations under .transforms/synthetic dir
diff --git a/src/com/android/providers/media/PickerUriResolver.java b/src/com/android/providers/media/PickerUriResolver.java
index b56b694..0b4fabf 100644
--- a/src/com/android/providers/media/PickerUriResolver.java
+++ b/src/com/android/providers/media/PickerUriResolver.java
@@ -19,6 +19,9 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Process.SYSTEM_UID;
 
+import static com.android.providers.media.AccessChecker.isRedactionNeededForPickerUri;
+import static com.android.providers.media.LocalUriMatcher.PICKER_GET_CONTENT_ID;
+import static com.android.providers.media.LocalUriMatcher.PICKER_ID;
 import static com.android.providers.media.photopicker.util.CursorUtils.getCursorString;
 import static com.android.providers.media.util.FileUtils.toFuseFile;
 
@@ -30,6 +33,7 @@
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.ParcelFileDescriptor;
@@ -58,7 +62,9 @@
 public class PickerUriResolver {
     private static final String TAG = "PickerUriResolver";
 
-    private static final String PICKER_SEGMENT = "picker";
+    public static final String PICKER_SEGMENT = "picker";
+
+    public static final String PICKER_GET_CONTENT_SEGMENT = "picker_get_content";
     private static final String PICKER_INTERNAL_SEGMENT = "picker_internal";
     /** A uri with prefix "content://media/picker" is considered as a picker uri */
     public static final Uri PICKER_URI = MediaStore.AUTHORITY_URI.buildUpon().
@@ -73,6 +79,7 @@
     public static final String REFRESH_PICKER_UI_PATH = "refresh_ui";
     public static final Uri REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI =
             PICKER_INTERNAL_URI.buildUpon().appendPath(REFRESH_PICKER_UI_PATH).build();
+    public static final String INIT_PATH = "init";
 
     public static final String MEDIA_PATH = "media";
     public static final String ALBUM_PATH = "albums";
@@ -84,23 +91,28 @@
     private final PickerDbFacade mDbFacade;
     private final Set<String> mAllValidProjectionColumns;
     private final String[] mAllValidProjectionColumnsArray;
+    private final LocalUriMatcher mLocalUriMatcher;
 
-    PickerUriResolver(Context context, PickerDbFacade dbFacade, ProjectionHelper projectionHelper) {
+    PickerUriResolver(Context context, PickerDbFacade dbFacade, ProjectionHelper projectionHelper,
+            LocalUriMatcher localUriMatcher) {
         mContext = context;
         mDbFacade = dbFacade;
         mAllValidProjectionColumns = projectionHelper.getProjectionMap(
                 MediaStore.PickerMediaColumns.class).keySet();
         mAllValidProjectionColumnsArray = mAllValidProjectionColumns.toArray(new String[0]);
+        mLocalUriMatcher = localUriMatcher;
     }
 
     public ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal,
-            int callingPid, int callingUid) throws FileNotFoundException {
+            LocalCallingIdentity localCallingIdentity)
+            throws FileNotFoundException {
         if (ParcelFileDescriptor.parseMode(mode) != ParcelFileDescriptor.MODE_READ_ONLY) {
             throw new SecurityException("PhotoPicker Uris can only be accessed to read."
                     + " Uri: " + uri);
         }
 
-        checkUriPermission(uri, callingPid, callingUid);
+        checkPermissionForRequireOriginalQueryParam(uri, localCallingIdentity);
+        checkUriPermission(uri, localCallingIdentity.pid, localCallingIdentity.uid);
 
         final ContentResolver resolver;
         try {
@@ -117,9 +129,10 @@
     }
 
     public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts,
-            CancellationSignal signal, int callingPid, int callingUid)
+            CancellationSignal signal, LocalCallingIdentity localCallingIdentity)
             throws FileNotFoundException {
-        checkUriPermission(uri, callingPid, callingUid);
+        checkPermissionForRequireOriginalQueryParam(uri, localCallingIdentity);
+        checkUriPermission(uri, localCallingIdentity.pid, localCallingIdentity.uid);
 
         final ContentResolver resolver;
         try {
@@ -210,7 +223,8 @@
                 + CloudMediaProviderContract.URI_PATH_SURFACE_CONTROLLER);
     }
 
-    private ParcelFileDescriptor openPickerFile(Uri uri) throws FileNotFoundException {
+    private ParcelFileDescriptor openPickerFile(Uri uri)
+            throws FileNotFoundException {
         final File file = getPickerFileFromUri(uri);
         if (file == null) {
             throw new FileNotFoundException("File not found for uri: " + uri);
@@ -235,19 +249,38 @@
 
     @VisibleForTesting
     Cursor queryPickerUri(Uri uri, String[] projection) {
+        String pickerSegmentType = getPickerSegmentType(uri);
         uri = unwrapProviderUri(uri);
-        return mDbFacade.queryMediaIdForApps(uri.getHost(), uri.getLastPathSegment(),
-                projection);
+        return mDbFacade.queryMediaIdForApps(pickerSegmentType, uri.getHost(),
+                uri.getLastPathSegment(), projection);
     }
 
-    public static Uri wrapProviderUri(Uri uri, int userId) {
+    private String getPickerSegmentType(Uri uri) {
+        switch (mLocalUriMatcher.matchUri(uri, /* allowHidden */ false)) {
+            case PICKER_ID:
+                return PICKER_SEGMENT;
+            case PICKER_GET_CONTENT_ID:
+                return PICKER_GET_CONTENT_SEGMENT;
+        }
+
+        return null;
+    }
+
+    /**
+     * Creates a picker uri incorporating authority, user id and cloud provider.
+     */
+    public static Uri wrapProviderUri(Uri uri, String action, int userId) {
         final List<String> segments = uri.getPathSegments();
         if (segments.size() != 2) {
             throw new IllegalArgumentException("Unexpected provider URI: " + uri);
         }
 
         Uri.Builder builder = initializeUriBuilder(MediaStore.AUTHORITY);
-        builder.appendPath(PICKER_SEGMENT);
+        if (action.equalsIgnoreCase(Intent.ACTION_GET_CONTENT)) {
+            builder.appendPath(PICKER_GET_CONTENT_SEGMENT);
+        } else {
+            builder.appendPath(PICKER_SEGMENT);
+        }
         builder.appendPath(String.valueOf(userId));
         builder.appendPath(uri.getHost());
 
@@ -293,10 +326,36 @@
     }
 
     private void checkUriPermission(Uri uri, int pid, int uid) {
-        if (!isSelf(uid) && mContext.checkUriPermission(uri, pid, uid,
+        // Clear query parameters to check for URI permissions, apps can add requireOriginal
+        // query parameter to URI, URI grants will not be present in that case.
+        Uri uriWithoutQueryParams = uri.buildUpon().clearQuery().build();
+        if (!isSelf(uid) && mContext.checkUriPermission(uriWithoutQueryParams, pid, uid,
                 Intent.FLAG_GRANT_READ_URI_PERMISSION) != PERMISSION_GRANTED) {
             throw new SecurityException("Calling uid ( " + uid + " ) does not have permission to " +
-                    "access picker uri: " + uri);
+                    "access picker uri: " + uriWithoutQueryParams);
+        }
+    }
+
+    private void checkPermissionForRequireOriginalQueryParam(Uri uri,
+            LocalCallingIdentity localCallingIdentity) {
+        String value = uri.getQueryParameter(MediaStore.PARAM_REQUIRE_ORIGINAL);
+        if (value == null || value.isEmpty()) {
+            return;
+        }
+
+        // Check if requireOriginal is set
+        if (Integer.parseInt(value) == 1) {
+            if (mLocalUriMatcher.matchUri(uri, /* allowHidden */ false) == PICKER_ID) {
+                throw new UnsupportedOperationException(
+                        "Require Original is not supported for Picker URI " + uri);
+            }
+
+            if (mLocalUriMatcher.matchUri(uri, /* allowHidden */ false) == PICKER_GET_CONTENT_ID
+                    && isRedactionNeededForPickerUri(localCallingIdentity)) {
+                throw new UnsupportedOperationException("Calling uid ( " + Binder.getCallingUid()
+                        + " ) does not have ACCESS_MEDIA_LOCATION permission for requesting "
+                        + "original file");
+            }
         }
     }
 
diff --git a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
index 48ee7fb..be7f3b1 100644
--- a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
+++ b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
@@ -132,6 +132,9 @@
     private int mToolbarHeight = 0;
     private boolean mShouldLogCancelledResult = true;
 
+    private AccessibilityManager mAccessibilityManager;
+    private boolean mIsAccessibilityEnabled;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         // This is required as GET_CONTENT with type "*/*" is also received by PhotoPicker due
@@ -185,6 +188,9 @@
 
         mTabLayout = findViewById(R.id.tab_layout);
 
+        mAccessibilityManager = getSystemService(AccessibilityManager.class);
+        mIsAccessibilityEnabled = mAccessibilityManager.isEnabled();
+
         initBottomSheetBehavior();
 
         // Save the fragment container layout so that we can adjust the padding based on preview or
@@ -201,6 +207,7 @@
         observeRefreshUiNotificationLiveData();
         // Restore state operation should always be kept at the end of this method.
         restoreState(savedInstanceState);
+
         // Call this after state is restored, to use the correct LOGGER_INSTANCE_ID_ARG
         if (savedInstanceState == null) {
             final String intentAction = intent != null ? intent.getAction() : null;
@@ -501,7 +508,7 @@
      */
     @VisibleForTesting
     protected boolean isAccessibilityEnabled() {
-        return getSystemService(AccessibilityManager.class).isEnabled();
+        return mIsAccessibilityEnabled;
     }
 
     private static int getBottomSheetPeekHeight(Context context) {
@@ -547,7 +554,7 @@
         logPickerSelectionConfirmed(mSelection.getSelectedItems().size());
         if (shouldPreloadSelectedItems()) {
             final var uris = PickerResult.getPickerUrisForItems(
-                    mSelection.getSelectedItems());
+                    getIntent().getAction(), mSelection.getSelectedItems());
             mPickerViewModel.logPreloadingStarted(uris.size());
             mPreloaderInstanceHolder.preloader =
                     SelectedMediaPreloader.preload(/* activity */ this, uris);
@@ -576,7 +583,8 @@
         // The permission controller will pass the requesting package's UID here
         final Bundle extras = getIntent().getExtras();
         final int uid = extras.getInt(Intent.EXTRA_UID);
-        final List<Uri> uris = getPickerUrisForItems(mSelection.getSelectedItemsWithoutGrants());
+        final List<Uri> uris = getPickerUrisForItems(getIntent().getAction(),
+                mSelection.getSelectedItemsWithoutGrants());
         if (!uris.isEmpty()) {
             ForegroundThread.getExecutor().execute(() -> {
                 // Handle grants in another thread to not block the UI.
@@ -589,7 +597,7 @@
         // deselected them.
         if (mPickerViewModel.isManagedSelectionEnabled()) {
             final List<Uri> urisForItemsWhoseGrantsNeedsToBeRevoked = getPickerUrisForItems(
-                    mSelection.getPreGrantedItemsToBeRevoked());
+                    getIntent().getAction(), mSelection.getPreGrantedItemsToBeRevoked());
             if (!urisForItemsWhoseGrantsNeedsToBeRevoked.isEmpty()) {
                 ForegroundThread.getExecutor().execute(() -> {
                     // Handle grants in another thread to not block the UI.
@@ -603,9 +611,8 @@
     }
 
     private void setResultForPickImagesOrGetContentAction() {
-        final Intent resultData = getPickerResponseIntent(
-                mSelection.canSelectMultiple(),
-                mSelection.getSelectedItems());
+        final Intent resultData = getPickerResponseIntent(getIntent().getAction(),
+                mSelection.canSelectMultiple(), mSelection.getSelectedItems());
         setResult(RESULT_OK, resultData);
     }
 
@@ -924,14 +931,14 @@
     /**
      * Reset to Photo Picker initial launch state (Photos grid tab) in the current profile mode.
      */
-    private void resetInCurrentProfile() {
+    private void resetInCurrentProfile(boolean shouldSendInitRequest) {
         // Clear all the fragments in the FragmentManager
         final FragmentManager fragmentManager = getSupportFragmentManager();
         fragmentManager.popBackStackImmediate(/* name */ null,
                 FragmentManager.POP_BACK_STACK_INCLUSIVE);
 
         // Reset all content in the current profile
-        mPickerViewModel.resetAllContentInCurrentProfile();
+        mPickerViewModel.resetAllContentInCurrentProfile(shouldSendInitRequest);
 
         // Set up the fragments same as the initial launch state
         setupInitialLaunchState();
@@ -1085,10 +1092,11 @@
      * refresh the UI.
      */
     private void observeRefreshUiNotificationLiveData() {
-        mPickerViewModel.shouldRefreshUiLiveData()
-                .observe(this, shouldRefresh -> {
-                    if (shouldRefresh && !mPickerViewModel.shouldShowOnlyLocalFeatures()) {
-                        resetInCurrentProfile();
+        mPickerViewModel.refreshUiLiveData()
+                .observe(this, refreshRequest -> {
+                    if (refreshRequest.shouldRefreshPicker()
+                            && !mPickerViewModel.shouldShowOnlyLocalFeatures()) {
+                        resetInCurrentProfile(refreshRequest.shouldInitPicker());
                     }
                 });
     }
diff --git a/src/com/android/providers/media/photopicker/PickerSyncController.java b/src/com/android/providers/media/photopicker/PickerSyncController.java
index eb7df4f..a5fba5c 100644
--- a/src/com/android/providers/media/photopicker/PickerSyncController.java
+++ b/src/com/android/providers/media/photopicker/PickerSyncController.java
@@ -26,6 +26,7 @@
 import static android.provider.CloudMediaProviderContract.MediaCollectionInfo.MEDIA_COLLECTION_ID;
 import static android.provider.MediaStore.MY_UID;
 
+import static com.android.providers.media.PickerUriResolver.INIT_PATH;
 import static com.android.providers.media.PickerUriResolver.PICKER_INTERNAL_URI;
 import static com.android.providers.media.PickerUriResolver.REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI;
 import static com.android.providers.media.PickerUriResolver.getDeletedMediaUri;
@@ -723,7 +724,7 @@
 
                     // Send UI refresh notification for any active picker sessions, as the
                     // UI data might be stale if a full sync needs to be run.
-                    sendPickerUiRefreshNotification();
+                    sendPickerUiRefreshNotification(/* isInitPending */ false);
 
                     final Bundle fullSyncQueryArgs = new Bundle();
                     if (enablePagedSync) {
@@ -1050,14 +1051,27 @@
                 Log.wtf(TAG, "CLOUD_PROVIDER_LOCK is already held by this thread.");
             }
 
-            sendPickerUiRefreshNotification();
+            sendPickerUiRefreshNotification(/* isInitPending */ true);
         }
     }
 
-    private void sendPickerUiRefreshNotification() {
-        ContentResolver contentResolver = mContext.getContentResolver();
+    /**
+     * Send Picker UI content observers a notification that a refresh is required.
+     * @param isInitPending when true, appends the URI path segment
+     *  {@link com.android.providers.media.PickerUriResolver.INIT_PATH} to the notification URI
+     *  to indicate that the UI that the cached picker data might be stale.
+     *  When a request notification is being sent from the sync path, set isInitPending as false to
+     *  prevent sending refresh notification in a loop.
+     */
+    private void sendPickerUiRefreshNotification(boolean isInitPending) {
+        final ContentResolver contentResolver = mContext.getContentResolver();
         if (contentResolver != null) {
-            contentResolver.notifyChange(REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI, null);
+            final Uri.Builder builder = REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI.buildUpon();
+            if (isInitPending) {
+                builder.appendPath(INIT_PATH);
+            }
+            final Uri refreshUri = builder.build();
+            contentResolver.notifyChange(refreshUri, null);
         } else {
             Log.d(TAG, "Couldn't notify the Picker UI to refresh");
         }
diff --git a/src/com/android/providers/media/photopicker/SelectedMediaPreloader.java b/src/com/android/providers/media/photopicker/SelectedMediaPreloader.java
index deefc1b..f6eb626 100644
--- a/src/com/android/providers/media/photopicker/SelectedMediaPreloader.java
+++ b/src/com/android/providers/media/photopicker/SelectedMediaPreloader.java
@@ -47,13 +47,9 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -262,23 +258,14 @@
 
         Trace.beginSection("Preloader.openFd");
 
-        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
-            try {
-                mContentResolver.openAssetFileDescriptor(uri, "r").close();
-            } catch (FileNotFoundException e) {
-                isOpenedSuccessfully.set(false);
-                Log.w(TAG, "Could not open FileDescriptor for " + uri, e);
-            } catch (IOException e) {
-                Log.w(TAG, "Failed to preload media file ", e);
-            }
-        });
-
         try {
-            future.get(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
-        } catch (TimeoutException e) {
-            return isOpenedSuccessfully.get();
-        } catch (InterruptedException | ExecutionException e) {
-            Log.w(TAG, "Could not preload the media item ", e);
+            mContentResolver.openAssetFileDescriptor(uri, "r").close();
+        } catch (FileNotFoundException e) {
+            isOpenedSuccessfully.set(false);
+            Log.w(TAG, "Could not open FileDescriptor for " + uri, e);
+        } catch (IOException e) {
+            isOpenedSuccessfully.set(false);
+            Log.w(TAG, "Failed to preload media file ", e);
         } finally {
             Trace.endSection();
 
diff --git a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
index 3fcdad9..50789b8 100644
--- a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
+++ b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
@@ -48,6 +48,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.providers.media.PickerUriResolver;
 import com.android.providers.media.photopicker.PickerSyncController;
 import com.android.providers.media.photopicker.data.model.Item;
 import com.android.providers.media.photopicker.sync.CloseableReentrantLock;
@@ -104,10 +105,7 @@
     private static final int FAIL = -1;
 
     private static final String TABLE_MEDIA = "media";
-    // Intentionally use /sdcard path so that the receiving app resolves it to it's per-user
-    // external storage path, e.g. /storage/emulated/<userid>. That way FUSE cross-user access is
-    // not required for picker paths sent across users
-    private static final String PICKER_PATH = "/sdcard/" + getPickerRelativePath();
+
     private static final String TABLE_ALBUM_MEDIA = "album_media";
 
     @VisibleForTesting
@@ -962,7 +960,7 @@
      * Returns a {@link Cursor} containing picker db media rows with columns as {@code projection},
      * a subset of {@link PickerMediaColumns}.
      */
-    public Cursor queryMediaIdForApps(String authority, String mediaId,
+    public Cursor queryMediaIdForApps(String pickerSegmentType, String authority, String mediaId,
             @NonNull String[] projection) {
         final String[] selectionArgs = new String[] { mediaId };
         final SQLiteQueryBuilder qb = createVisibleMediaQueryBuilder();
@@ -973,13 +971,13 @@
         }
 
         if (authority.equals(mLocalProvider)) {
-            return queryMediaIdForAppsLocked(qb, projection, selectionArgs);
+            return queryMediaIdForAppsLocked(qb, projection, selectionArgs, pickerSegmentType);
         }
 
         try (CloseableReentrantLock ignored = mPickerSyncLockManager
                 .lock(PickerSyncLockManager.DB_CLOUD_LOCK)) {
             if (authority.equals(mCloudProvider)) {
-                return queryMediaIdForAppsLocked(qb, projection, selectionArgs);
+                return queryMediaIdForAppsLocked(qb, projection, selectionArgs, pickerSegmentType);
             }
         }
 
@@ -987,8 +985,9 @@
     }
 
     private Cursor queryMediaIdForAppsLocked(@NonNull SQLiteQueryBuilder qb,
-            @NonNull String[] projection, @NonNull String[] selectionArgs) {
-        return qb.query(mDatabase, getMediaStoreProjectionLocked(projection),
+            @NonNull String[] projection, @NonNull String[] selectionArgs,
+            String pickerSegmentType) {
+        return qb.query(mDatabase, getMediaStoreProjectionLocked(projection, pickerSegmentType),
                 /* selection */ null, selectionArgs, /* groupBy */ null, /* having */ null,
                 /* orderBy */ null, /* limitStr */ null);
     }
@@ -1127,7 +1126,7 @@
     private String[] getCloudMediaProjectionLocked() {
         return new String[] {
             getProjectionAuthorityLocked(),
-            getProjectionDataLocked(MediaColumns.DATA),
+            getProjectionDataLocked(MediaColumns.DATA, PickerUriResolver.PICKER_SEGMENT),
             getProjectionId(MediaColumns.ID),
             // The id in the picker.db table represents the row id. This is used in UI pagination.
             getProjectionSimple(KEY_ID, Item.ROW_ID),
@@ -1141,13 +1140,14 @@
         };
     }
 
-    private String[] getMediaStoreProjectionLocked(String[] columns) {
+    private String[] getMediaStoreProjectionLocked(String[] columns, String pickerSegmentType) {
         final String[] projection = new String[columns.length];
 
         for (int i = 0; i < projection.length; i++) {
             switch (columns[i]) {
                 case PickerMediaColumns.DATA:
-                    projection[i] = getProjectionDataLocked(PickerMediaColumns.DATA);
+                    projection[i] = getProjectionDataLocked(PickerMediaColumns.DATA,
+                            pickerSegmentType);
                     break;
                 case PickerMediaColumns.DISPLAY_NAME:
                     projection[i] =
@@ -1202,13 +1202,13 @@
                 KEY_CLOUD_ID, mLocalProvider, mCloudProvider, MediaColumns.AUTHORITY);
     }
 
-    private String getProjectionDataLocked(String asColumn) {
+    private String getProjectionDataLocked(String asColumn, String pickerSegmentType) {
         // _data format:
         // /sdcard/.transforms/synthetic/picker/<user-id>/<authority>/media/<display-name>
         // See PickerUriResolver#getMediaUri
         final String authority = String.format("CASE WHEN %s IS NULL THEN '%s' ELSE '%s' END",
                 KEY_CLOUD_ID, mLocalProvider, mCloudProvider);
-        final String fullPath = "'" + PICKER_PATH + "/'"
+        final String fullPath = "'" + getPickerPath(pickerSegmentType) + "/'"
                 + "||" + "'" + MediaStore.MY_USER_ID + "/'"
                 + "||" + authority
                 + "||" + "'/" + CloudMediaProviderContract.URI_PATH_MEDIA + "/'"
@@ -1216,6 +1216,13 @@
         return String.format("%s AS %s", fullPath, asColumn);
     }
 
+    private String getPickerPath(String pickerSegmentType) {
+        // Intentionally use /sdcard path so that the receiving app resolves it to its per-user
+        // external storage path, e.g. /storage/emulated/<userid>. That way FUSE cross-user
+        // access is not required for picker paths sent across users
+        return "/sdcard/" + getPickerRelativePath(pickerSegmentType);
+    }
+
     private String getProjectionId(String asColumn) {
         // We prefer cloud_id first and it only matters for cloud+local items. For those, the row
         // will already be associated with a cloud authority, see #getProjectionAuthorityLocked.
diff --git a/src/com/android/providers/media/photopicker/data/PickerResult.java b/src/com/android/providers/media/photopicker/data/PickerResult.java
index 7bea27c..8108afa 100644
--- a/src/com/android/providers/media/photopicker/data/PickerResult.java
+++ b/src/com/android/providers/media/photopicker/data/PickerResult.java
@@ -40,10 +40,10 @@
      * @return {@code Intent} which contains Uri that has been granted access on.
      */
     @NonNull
-    public static Intent getPickerResponseIntent(boolean canSelectMultiple,
+    public static Intent getPickerResponseIntent(String action, boolean canSelectMultiple,
             @NonNull List<Item> selectedItems) {
         // 1. Get Picker Uris corresponding to the selected items
-        List<Uri> selectedUris = getPickerUrisForItems(selectedItems);
+        List<Uri> selectedUris = getPickerUrisForItems(action, selectedItems);
 
         // 2. Grant read access to picker Uris and return
         Intent intent = new Intent();
@@ -71,22 +71,23 @@
     }
 
     @VisibleForTesting
-    static Uri getPickerUri(Uri uri) {
+    static Uri getPickerUri(String action, Uri uri) {
         final String userInfo = uri.getUserInfo();
         final String userId = userInfo == null ? UserId.CURRENT_USER.toString() : userInfo;
-        return PickerUriResolver.wrapProviderUri(uri, Integer.parseInt(userId));
+        return PickerUriResolver.wrapProviderUri(uri, action, Integer.parseInt(userId));
     }
 
     /**
      * Returns list of PhotoPicker Uris corresponding to each {@link Item}
      *
+     * @param action action name which opened PhotoPicker
      * @param items list of Item for which we return uri list.
      */
     @NonNull
-    public static List<Uri> getPickerUrisForItems(@NonNull List<Item> items) {
+    public static List<Uri> getPickerUrisForItems(String action, @NonNull List<Item> items) {
         List<Uri> uris = new ArrayList<>();
         for (Item item : items) {
-            uris.add(getPickerUri(item.getContentUri()));
+            uris.add(getPickerUri(action, item.getContentUri()));
         }
 
         return uris;
diff --git a/src/com/android/providers/media/photopicker/data/model/RefreshRequest.java b/src/com/android/providers/media/photopicker/data/model/RefreshRequest.java
new file mode 100644
index 0000000..5b4c3c6
--- /dev/null
+++ b/src/com/android/providers/media/photopicker/data/model/RefreshRequest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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.providers.media.photopicker.data.model;
+
+public class RefreshRequest {
+    public static RefreshRequest DEFAULT = new RefreshRequest(false, false);
+    private boolean mShouldRefresh;
+    private boolean mShouldInit;
+
+    public RefreshRequest(boolean shouldRefresh, boolean shouldInit) {
+        mShouldRefresh = shouldRefresh;
+        mShouldInit = shouldInit;
+    }
+
+    /**
+     * Returns true if Photo Picker UI should be refreshed, otherwise returns false.
+     */
+    public boolean shouldRefreshPicker() {
+        return mShouldRefresh;
+    }
+
+    /**
+     *  Returns true if Photo Picker data might be stale and should be initialized, otherwise
+     *  returns false.
+     */
+    public boolean shouldInitPicker() {
+        return mShouldInit;
+    }
+}
diff --git a/src/com/android/providers/media/photopicker/ui/TabContainerFragment.java b/src/com/android/providers/media/photopicker/ui/TabContainerFragment.java
index 8e070b5..8f06fb5 100644
--- a/src/com/android/providers/media/photopicker/ui/TabContainerFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/TabContainerFragment.java
@@ -18,6 +18,7 @@
 import static com.android.providers.media.util.MimeUtils.isVideoMimeType;
 
 import android.os.Bundle;
+import android.provider.MediaStore;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -68,11 +69,17 @@
     @Override
     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
-        mTabContainerAdapter = new TabContainerAdapter(/* fragment */ this);
         mViewPager = view.findViewById(R.id.picker_tab_viewpager);
-        mViewPager.setAdapter(mTabContainerAdapter);
         final ViewModelProvider viewModelProvider = new ViewModelProvider(requireActivity());
         mPickerViewModel = viewModelProvider.get(PickerViewModel.class);
+        mTabContainerAdapter = new TabContainerAdapter(/* fragment */ this);
+        mViewPager.setAdapter(mTabContainerAdapter);
+
+        // Launch in albums tab if the app requests so
+        if (mPickerViewModel.getPickerLaunchTab() == MediaStore.PICK_IMAGES_TAB_ALBUMS) {
+            // Launch the picker in Albums tab without any switch animation
+            mViewPager.setCurrentItem(ALBUMS_TAB_POSITION, /* smoothScroll */ false);
+        }
 
         // If the ViewPager2 has more than one page with BottomSheetBehavior, the scrolled view
         // (e.g. RecyclerView) on the second page can't be scrolled. The workaround is to update
diff --git a/src/com/android/providers/media/photopicker/ui/settings/SettingsCloudMediaSelectFragment.java b/src/com/android/providers/media/photopicker/ui/settings/SettingsCloudMediaSelectFragment.java
index f08bd75..7d05864 100644
--- a/src/com/android/providers/media/photopicker/ui/settings/SettingsCloudMediaSelectFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/settings/SettingsCloudMediaSelectFragment.java
@@ -139,7 +139,8 @@
                             providerMediaCollectionInfo.getAccountConfigurationIntent();
                     selectedPref.setExtraWidgetOnClickListener(
                             accountConfigurationIntent == null ? null : v ->
-                                    requireActivity().startActivity(accountConfigurationIntent));
+                                    requireActivity().startActivityAsUser(
+                                            accountConfigurationIntent, mUserId.getUserHandle()));
                 });
     }
 
diff --git a/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java b/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
index ff5e5c0..69f138f 100644
--- a/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
+++ b/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
@@ -24,6 +24,7 @@
 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_SCREENSHOTS;
 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS;
 
+import static com.android.providers.media.PickerUriResolver.INIT_PATH;
 import static com.android.providers.media.PickerUriResolver.REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI;
 import static com.android.providers.media.photopicker.DataLoaderThread.TOKEN;
 import static com.android.providers.media.photopicker.PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY;
@@ -81,6 +82,7 @@
 import com.android.providers.media.photopicker.data.UserIdManager;
 import com.android.providers.media.photopicker.data.model.Category;
 import com.android.providers.media.photopicker.data.model.Item;
+import com.android.providers.media.photopicker.data.model.RefreshRequest;
 import com.android.providers.media.photopicker.data.model.UserId;
 import com.android.providers.media.photopicker.metrics.NonUiEventLogger;
 import com.android.providers.media.photopicker.metrics.PhotoPickerUiEventLogger;
@@ -103,9 +105,6 @@
  */
 public class PickerViewModel extends AndroidViewModel {
     public static final String TAG = "PhotoPicker";
-
-    private static final int RECENT_MINIMUM_COUNT = 12;
-
     private static final int INSTANCE_ID_MAX = 1 << 15;
     private static final int DELAY_MILLIS = 0;
 
@@ -122,6 +121,8 @@
 
     private final MuteStatus mMuteStatus;
     public boolean mEmptyPageDisplayed = false;
+    @MediaStore.PickImagesTab
+    private int mPickerLaunchTab = MediaStore.PICK_IMAGES_TAB_IMAGES;
 
     // TODO(b/193857982): We keep these four data sets now, we may need to find a way to reduce the
     //  data set to reduce memories.
@@ -138,11 +139,13 @@
     private MutableLiveData<List<Category>> mCategoryList;
 
     private MutableLiveData<Boolean> mIsAllPreGrantedMediaLoaded = new MutableLiveData<>(false);
-    private final MutableLiveData<Boolean> mShouldRefreshUiLiveData = new MutableLiveData<>(false);
+    private final MutableLiveData<RefreshRequest> mRefreshUiLiveData =
+            new MutableLiveData<>(RefreshRequest.DEFAULT);
     private final ContentObserver mRefreshUiNotificationObserver = new ContentObserver(null) {
         @Override
-        public void onChange(boolean selfChange) {
-            mShouldRefreshUiLiveData.postValue(true);
+        public void onChange(boolean selfChange, Uri uri) {
+            boolean shouldInit = uri.getLastPathSegment().equals(INIT_PATH);
+            mRefreshUiLiveData.postValue(new RefreshRequest(true, shouldInit));
         }
     };
 
@@ -227,6 +230,14 @@
         }
     }
 
+    public int getPickerLaunchTab() {
+        return mPickerLaunchTab;
+    }
+
+    public void setPickerLaunchTab(int launchTab) {
+        mPickerLaunchTab = launchTab;
+    }
+
     @VisibleForTesting
     protected void initConfigStore() {
         mConfigStore = MediaApplication.getConfigStore();
@@ -363,22 +374,24 @@
     @UiThread
     public void onSwitchedProfile() {
         resetRefreshUiNotificationObserver();
-        resetAllContentInCurrentProfile();
+        resetAllContentInCurrentProfile(/* shouldSendInitRequest */ true);
     }
 
     /**
      * Reset all the content (items, categories & banners) in the current profile.
      */
     @UiThread
-    public void resetAllContentInCurrentProfile() {
+    public void resetAllContentInCurrentProfile(boolean shouldSendInitRequest) {
         Log.d(TAG, "Reset all content in current profile");
 
         // Post 'should refresh UI live data' value as false to avoid unnecessary repetitive resets
-        mShouldRefreshUiLiveData.postValue(false);
+        mRefreshUiLiveData.postValue(RefreshRequest.DEFAULT);
 
         clearQueuedTasksInDataLoaderThread();
 
-        initPhotoPickerData();
+        if (shouldSendInitRequest) {
+            initPhotoPickerData();
+        }
 
         // Clear the existing content - selection, photos grid, albums grid, banners
         mSelection.clearSelectedItems();
@@ -863,6 +876,23 @@
      * Parse values from {@code intent} and set corresponding fields
      */
     public void parseValuesFromIntent(Intent intent) throws IllegalArgumentException {
+        final Bundle extras = intent.getExtras();
+        if (extras != null && extras.containsKey(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB)) {
+            if (intent.getAction().equals(ACTION_GET_CONTENT)) {
+                Log.e(TAG, "EXTRA_PICKER_LAUNCH_TAB cannot be passed as an extra in "
+                        + "ACTION_GET_CONTENT");
+            } else if (intent.getAction().equals(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP)) {
+                throw new IllegalArgumentException("EXTRA_PICKER_LAUNCH_TAB cannot be passed as an "
+                        + "extra in ACTION_USER_SELECT_IMAGES_FOR_APP");
+            } else {
+                mPickerLaunchTab = extras.getInt(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB);
+                if (!checkPickerLaunchOptionValidity(mPickerLaunchTab)) {
+                    throw new IllegalArgumentException("Incorrect value " + mPickerLaunchTab
+                            + " received for the intent extra: "
+                            + MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB);
+                }
+            }
+        }
         mUserIdManager.setIntentAndCheckRestrictions(intent);
 
         mMimeTypeFilters = MimeFilterUtils.getMimeTypeFilters(intent);
@@ -899,6 +929,11 @@
         }
     }
 
+    private boolean checkPickerLaunchOptionValidity(int launchOption) {
+        return launchOption == MediaStore.PICK_IMAGES_TAB_IMAGES
+                || launchOption == MediaStore.PICK_IMAGES_TAB_ALBUMS;
+    }
+
     private void initBannerManager() {
         mBannerManager = shouldShowOnlyLocalFeatures()
                 ? new BannerManager(mAppContext, mUserIdManager, mConfigStore)
@@ -1398,14 +1433,14 @@
      * @return a {@link LiveData} that posts Should Refresh Picker UI as {@code true} when notified.
      */
     @NonNull
-    public LiveData<Boolean> shouldRefreshUiLiveData() {
-        return mShouldRefreshUiLiveData;
+    public LiveData<RefreshRequest> refreshUiLiveData() {
+        return mRefreshUiLiveData;
     }
 
     private void registerRefreshUiNotificationObserver() {
         mContentResolver = getContentResolverForSelectedUser();
         mContentResolver.registerContentObserver(REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI,
-                /* notifyForDescendants */ false, mRefreshUiNotificationObserver);
+                /* notifyForDescendants */ true, mRefreshUiNotificationObserver);
     }
 
     private void unregisterRefreshUiNotificationObserver() {
diff --git a/src/com/android/providers/media/util/IsoInterface.java b/src/com/android/providers/media/util/IsoInterface.java
index 8da64b9..5fb5130 100644
--- a/src/com/android/providers/media/util/IsoInterface.java
+++ b/src/com/android/providers/media/util/IsoInterface.java
@@ -261,6 +261,9 @@
             }
         } catch (ErrnoException e) {
             throw e.rethrowAsIOException();
+        } catch (OutOfMemoryError e) {
+            Log.e(TAG, "Too many boxes in file. This might imply a corrupted file.", e);
+            throw new IOException(e.getMessage());
         }
 
         // Also create a flattened structure to speed up searching
@@ -295,8 +298,8 @@
     public @NonNull long[] getBoxRanges(int type) {
         LongArray res = new LongArray();
         for (Box box : mFlattened) {
-            for (int i = 0; i < box.range.length; i += 2) {
-                if (box.type == type) {
+            if (box.type == type) {
+                for (int i = 0; i < box.range.length; i += 2) {
                     res.add(box.range[i] + box.headerSize);
                     res.add(box.range[i] + box.range[i + 1]);
                 }
@@ -308,8 +311,8 @@
     public @NonNull long[] getBoxRanges(@NonNull UUID uuid) {
         LongArray res = new LongArray();
         for (Box box : mFlattened) {
-            for (int i = 0; i < box.range.length; i += 2) {
-                if (box.type == BOX_UUID && Objects.equals(box.uuid, uuid)) {
+            if (box.type == BOX_UUID && Objects.equals(box.uuid, uuid)) {
+                for (int i = 0; i < box.range.length; i += 2) {
                     res.add(box.range[i] + box.headerSize);
                     res.add(box.range[i] + box.range[i + 1]);
                 }
diff --git a/src/com/android/providers/media/util/SyntheticPathUtils.java b/src/com/android/providers/media/util/SyntheticPathUtils.java
index aa0db93..6d73802 100644
--- a/src/com/android/providers/media/util/SyntheticPathUtils.java
+++ b/src/com/android/providers/media/util/SyntheticPathUtils.java
@@ -16,14 +16,17 @@
 
 package com.android.providers.media.util;
 
+import static com.android.providers.media.PickerUriResolver.PICKER_GET_CONTENT_SEGMENT;
+import static com.android.providers.media.PickerUriResolver.PICKER_SEGMENT;
 import static com.android.providers.media.util.FileUtils.buildPath;
 import static com.android.providers.media.util.FileUtils.buildPrimaryVolumeFile;
 import static com.android.providers.media.util.FileUtils.extractFileName;
 
-import androidx.annotation.VisibleForTesting;
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.VisibleForTesting;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
@@ -37,7 +40,6 @@
     private static final String TRANSFORMS_DIR = ".transforms";
     private static final String SYNTHETIC_DIR = "synthetic";
     private static final String REDACTED_DIR = "redacted";
-    private static final String PICKER_DIR = "picker";
 
     public static final String REDACTED_URI_ID_PREFIX = "RUID";
     public static final int REDACTED_URI_ID_SIZE = 36;
@@ -48,8 +50,12 @@
         return buildPath(/* base */ null, TRANSFORMS_DIR, SYNTHETIC_DIR, REDACTED_DIR).getPath();
     }
 
-    public static String getPickerRelativePath() {
-        return buildPath(/* base */ null, TRANSFORMS_DIR, SYNTHETIC_DIR, PICKER_DIR).getPath();
+    /**
+     * Returns picker synthetic path directory.
+     */
+    public static String getPickerRelativePath(String pickerSegmentType) {
+        return buildPath(/* base */ null, TRANSFORMS_DIR, SYNTHETIC_DIR,
+                pickerSegmentType).getPath();
     }
 
     public static boolean isRedactedPath(String path, int userId) {
@@ -66,10 +72,13 @@
     }
 
     public static boolean isPickerPath(String path, int userId) {
-        final String pickerDir = buildPrimaryVolumeFile(userId, getPickerRelativePath())
-                .getAbsolutePath();
+        final String pickerDir = buildPrimaryVolumeFile(userId, getPickerRelativePath(
+                PICKER_SEGMENT)).getAbsolutePath();
+        final String pickerGetContentDir = buildPrimaryVolumeFile(userId,
+                getPickerRelativePath(PICKER_GET_CONTENT_SEGMENT)).getAbsolutePath();
 
-        return path != null && startsWith(path, pickerDir);
+        return path != null && (startsWith(path, pickerDir) || startsWith(path,
+                pickerGetContentDir));
     }
 
     public static boolean isSyntheticPath(String path, int userId) {
diff --git a/tests/src/com/android/providers/media/AccessCheckerTest.java b/tests/src/com/android/providers/media/AccessCheckerTest.java
index 49a870e..ef6c963 100644
--- a/tests/src/com/android/providers/media/AccessCheckerTest.java
+++ b/tests/src/com/android/providers/media/AccessCheckerTest.java
@@ -29,6 +29,7 @@
 import static com.android.providers.media.AccessChecker.getWhereForUserSelectedAccess;
 import static com.android.providers.media.AccessChecker.hasAccessToCollection;
 import static com.android.providers.media.AccessChecker.hasUserSelectedAccess;
+import static com.android.providers.media.AccessChecker.isRedactionNeededForPickerUri;
 import static com.android.providers.media.LocalUriMatcher.AUDIO_MEDIA;
 import static com.android.providers.media.LocalUriMatcher.DOWNLOADS;
 import static com.android.providers.media.LocalUriMatcher.DOWNLOADS_ID;
@@ -45,7 +46,9 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 
 import android.os.Bundle;
 import android.system.Os;
@@ -398,6 +401,28 @@
     }
 
     @Test
+    public void testIsRedactionNeededForPickerUri_returnsFalse_withNoRedactPerms() {
+        LocalCallingIdentity callingIdentityWithRedactionNotNeededPermission =
+                LocalCallingIdentity.forTest(
+                        InstrumentationRegistry.getTargetContext(), Os.getuid(),
+                        ~LocalCallingIdentity.PERMISSION_IS_REDACTION_NEEDED);
+
+        assertFalse("App with write perms should get non redacted data",
+                isRedactionNeededForPickerUri(callingIdentityWithRedactionNotNeededPermission));
+    }
+
+    @Test
+    public void testIsRedactionNeededForPickerUri_returnsTrue_withRedactPerms() {
+        LocalCallingIdentity callingIdentityWithRedactionNeededPermission =
+                LocalCallingIdentity.forTest(
+                        InstrumentationRegistry.getTargetContext(), Os.getuid(),
+                        LocalCallingIdentity.PERMISSION_IS_REDACTION_NEEDED);
+
+        assertTrue("App with no perms should get redacted data",
+                isRedactionNeededForPickerUri(callingIdentityWithRedactionNeededPermission));
+    }
+
+    @Test
     public void testGetWhereForConstrainedAccess_forWrite_hasLegacyWrite() {
         LocalCallingIdentity hasLegacyWrite = LocalCallingIdentity.forTest(
                 InstrumentationRegistry.getTargetContext(), Os.getuid(),
diff --git a/tests/src/com/android/providers/media/LocalUriMatcherTest.java b/tests/src/com/android/providers/media/LocalUriMatcherTest.java
index 32721ed..01ce3ec 100644
--- a/tests/src/com/android/providers/media/LocalUriMatcherTest.java
+++ b/tests/src/com/android/providers/media/LocalUriMatcherTest.java
@@ -43,6 +43,15 @@
                 LocalUriMatcher.PICKER_ID,
                 assembleTestUri(new String[] {"picker", "0", "anything", "media", "anything"}));
 
+        assertMatchesPublic(
+                LocalUriMatcher.PICKER_GET_CONTENT_ID,
+                assembleTestUri(new String[]{"picker_get_content", Integer.toString(1),
+                        Integer.toString(1)}));
+        assertMatchesPublic(
+                LocalUriMatcher.PICKER_GET_CONTENT_ID,
+                assembleTestUri(
+                        new String[]{"picker_get_content", "0", "anything", "media", "anything"}));
+
         assertMatchesPublic(LocalUriMatcher.CLI, assembleTestUri(new String[] {"cli"}));
 
         assertMatchesPublic(
@@ -204,6 +213,15 @@
                 LocalUriMatcher.PICKER_ID,
                 assembleTestUri(new String[] {"picker", "0", "anything", "media", "anything"}));
 
+        assertMatchesHidden(
+                LocalUriMatcher.PICKER_GET_CONTENT_ID,
+                assembleTestUri(new String[]{"picker_get_content", Integer.toString(1),
+                        Integer.toString(1)}));
+        assertMatchesHidden(
+                LocalUriMatcher.PICKER_GET_CONTENT_ID,
+                assembleTestUri(
+                        new String[]{"picker_get_content", "0", "anything", "media", "anything"}));
+
         assertMatchesHidden(LocalUriMatcher.CLI, assembleTestUri(new String[] {"cli"}));
 
         assertMatchesHidden(
diff --git a/tests/src/com/android/providers/media/PickerUriResolverTest.java b/tests/src/com/android/providers/media/PickerUriResolverTest.java
index 3d8c260..e1d1016 100644
--- a/tests/src/com/android/providers/media/PickerUriResolverTest.java
+++ b/tests/src/com/android/providers/media/PickerUriResolverTest.java
@@ -16,8 +16,10 @@
 
 package com.android.providers.media;
 
+import static android.content.Intent.ACTION_GET_CONTENT;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.provider.MediaStore.ACTION_PICK_IMAGES;
 
 import static androidx.test.InstrumentationRegistry.getContext;
 import static androidx.test.InstrumentationRegistry.getTargetContext;
@@ -26,6 +28,7 @@
 
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -78,10 +81,13 @@
     private static Uri sTestPickerUri;
     private static String TEST_ID;
 
+    private static Uri sMediaStoreUriInOtherContext;
+
     private static class TestPickerUriResolver extends PickerUriResolver {
         TestPickerUriResolver(Context context) {
             super(context, new PickerDbFacade(getTargetContext(), new PickerSyncLockManager()),
-                    new ProjectionHelper(Column.class, ExportedSince.class));
+                    new ProjectionHelper(Column.class, ExportedSince.class),
+                    new LocalUriMatcher(MediaStore.AUTHORITY));
         }
 
         @Override
@@ -119,14 +125,16 @@
                         Manifest.permission.INTERACT_ACROSS_USERS);
         sCurrentContext = mock(Context.class);
         when(sCurrentContext.getUser()).thenReturn(UserHandle.of(UserHandle.myUserId()));
+        PackageManager packageManager = mock(PackageManager.class);
+        when(sCurrentContext.getPackageManager()).thenReturn(packageManager);
+        when(packageManager.getPackagesForUid(anyInt())).thenReturn(
+                new String[]{getContext().getPackageName()});
 
         final Context otherUserContext = createOtherUserContext(TEST_USER);
         sTestPickerUriResolver = new TestPickerUriResolver(sCurrentContext);
 
-        final Uri mediaStoreUriInOtherContext = createTestFileInContext(otherUserContext);
-        TEST_ID = mediaStoreUriInOtherContext.getLastPathSegment();
-        sTestPickerUri = getPickerUriForId(ContentUris.parseId(mediaStoreUriInOtherContext),
-                TEST_USER);
+        sMediaStoreUriInOtherContext = createTestFileInContext(otherUserContext);
+        TEST_ID = sMediaStoreUriInOtherContext.getLastPathSegment();
     }
 
     @AfterClass
@@ -136,24 +144,37 @@
 
     @Test
     public void wrapProviderUriValid() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
         final String providerSuffix = "authority/media/media_id";
 
         final Uri providerUriUserImplicit = Uri.parse("content://" + providerSuffix);
 
         final Uri providerUriUser0 = Uri.parse("content://0@" + providerSuffix);
         final Uri mediaUriUser0 = Uri.parse("content://media/picker/0/" + providerSuffix);
+        final Uri mediaUriUser0PickerGetContent = Uri.parse(
+                "content://media/picker_get_content/0/" + providerSuffix);
 
         final Uri providerUriUser10 = Uri.parse("content://10@" + providerSuffix);
         final Uri mediaUriUser10 = Uri.parse("content://media/picker/10/" + providerSuffix);
 
-        assertThat(PickerUriResolver.wrapProviderUri(providerUriUserImplicit, 0))
+        assertThat(PickerUriResolver.wrapProviderUri(providerUriUserImplicit,
+                ACTION_PICK_IMAGES, 0))
                 .isEqualTo(mediaUriUser0);
-        assertThat(PickerUriResolver.wrapProviderUri(providerUriUser0, 0)).isEqualTo(mediaUriUser0);
+        assertThat(PickerUriResolver.wrapProviderUri(providerUriUserImplicit,
+                ACTION_GET_CONTENT, 0))
+                .isEqualTo(mediaUriUser0PickerGetContent);
+        assertThat(
+                PickerUriResolver.wrapProviderUri(providerUriUser0, ACTION_PICK_IMAGES,
+                        0)).isEqualTo(mediaUriUser0);
         assertThat(PickerUriResolver.unwrapProviderUri(mediaUriUser0)).isEqualTo(providerUriUser0);
 
-        assertThat(PickerUriResolver.wrapProviderUri(providerUriUserImplicit, 10))
+        assertThat(PickerUriResolver.wrapProviderUri(providerUriUserImplicit,
+                ACTION_PICK_IMAGES, 10))
                 .isEqualTo(mediaUriUser10);
-        assertThat(PickerUriResolver.wrapProviderUri(providerUriUser10, 10))
+        assertThat(
+                PickerUriResolver.wrapProviderUri(providerUriUser10, ACTION_PICK_IMAGES,
+                        10))
                 .isEqualTo(mediaUriUser10);
         assertThat(PickerUriResolver.unwrapProviderUri(mediaUriUser10))
                 .isEqualTo(providerUriUser10);
@@ -161,6 +182,8 @@
 
     @Test
     public void wrapProviderUriInvalid() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
         final String providerSuffixLong = "authority/media/media_id/another_media_id";
         final String providerSuffixShort = "authority/media";
 
@@ -171,18 +194,22 @@
         final Uri mediaUriUserShort = Uri.parse("content://media/picker/0/" + providerSuffixShort);
 
         assertThrows(IllegalArgumentException.class,
-                () -> PickerUriResolver.wrapProviderUri(providerUriUserLong, 0));
+                () -> PickerUriResolver.wrapProviderUri(providerUriUserLong, ACTION_PICK_IMAGES,
+                        0));
         assertThrows(IllegalArgumentException.class,
                 () -> PickerUriResolver.unwrapProviderUri(mediaUriUserLong));
 
         assertThrows(IllegalArgumentException.class,
                 () -> PickerUriResolver.unwrapProviderUri(mediaUriUserShort));
         assertThrows(IllegalArgumentException.class,
-                () -> PickerUriResolver.wrapProviderUri(providerUriUserShort, 0));
+                () -> PickerUriResolver.wrapProviderUri(providerUriUserShort, ACTION_PICK_IMAGES,
+                        0));
     }
 
     @Test
     public void testGetAlbumUri() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
         final String authority = "foo";
         final Uri uri = Uri.parse("content://foo/album");
         assertThat(PickerUriResolver.getAlbumUri(authority)).isEqualTo(uri);
@@ -190,6 +217,8 @@
 
     @Test
     public void testGetMediaUri() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
         final String authority = "foo";
         final Uri uri = Uri.parse("content://foo/media");
         assertThat(PickerUriResolver.getMediaUri(authority)).isEqualTo(uri);
@@ -197,6 +226,8 @@
 
     @Test
     public void testGetDeletedMediaUri() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
         final String authority = "foo";
         final Uri uri = Uri.parse("content://foo/deleted_media");
         assertThat(PickerUriResolver.getDeletedMediaUri(authority)).isEqualTo(uri);
@@ -204,6 +235,8 @@
 
     @Test
     public void testCreateSurfaceControllerUri() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
         final String authority = "foo";
         final Uri uri = Uri.parse("content://foo/surface_controller");
         assertThat(PickerUriResolver.createSurfaceControllerUri(authority)).isEqualTo(uri);
@@ -211,10 +244,13 @@
 
     @Test
     public void testOpenFile_mode_w() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
         updateReadUriPermission(sTestPickerUri, /* grant */ true);
         try {
             sTestPickerUriResolver.openFile(sTestPickerUri, "w", /* signal */ null,
-                    /* callingPid */ -1, /* callingUid */ -1);
+                    LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                            0));
             fail("Write is not supported for Picker Uris. uri: " + sTestPickerUri);
         } catch (SecurityException expected) {
             // expected
@@ -225,10 +261,13 @@
 
     @Test
     public void testOpenFile_mode_rw() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
         updateReadUriPermission(sTestPickerUri, /* grant */ true);
         try {
             sTestPickerUriResolver.openFile(sTestPickerUri, "rw", /* signal */ null,
-                    /* callingPid */ -1, /* callingUid */ -1);
+                    LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                            0));
             fail("Read-Write is not supported for Picker Uris. uri: " + sTestPickerUri);
         } catch (SecurityException expected) {
             // expected
@@ -239,10 +278,13 @@
 
     @Test
     public void testOpenFile_mode_invalid() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
         updateReadUriPermission(sTestPickerUri, /* grant */ true);
         try {
             sTestPickerUriResolver.openFile(sTestPickerUri, "foo", /* signal */ null,
-                    /* callingPid */ -1, /* callingUid */ -1);
+                    LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                            0));
             fail("Invalid mode should not be supported for openFile. uri: " + sTestPickerUri);
         } catch (IllegalArgumentException expected) {
             // expected
@@ -252,6 +294,8 @@
 
     @Test
     public void testPickerUriResolver_permissionDenied() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
         updateReadUriPermission(sTestPickerUri, /* grant */ false);
 
         testOpenFile_permissionDenied(sTestPickerUri);
@@ -262,13 +306,15 @@
 
     @Test
     public void testPermissionGrantedOnOtherUserUri() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
         // This test requires the uri to be valid in 2 different users, but the permission is
         // granted in one user only.
         final int otherUserId = 50;
         final Context otherUserContext = createOtherUserContext(otherUserId);
         final Uri mediaStoreUserInAnotherValidUser = createTestFileInContext(otherUserContext);
         final Uri grantedUri = getPickerUriForId(ContentUris.parseId(
-                mediaStoreUserInAnotherValidUser), otherUserId);
+                mediaStoreUserInAnotherValidUser), otherUserId, ACTION_PICK_IMAGES);
         updateReadUriPermission(grantedUri, /* grant */ true);
 
         final Uri deniedUri = sTestPickerUri;
@@ -282,9 +328,12 @@
 
     @Test
     public void testPickerUriResolver_userInvalid() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
         final int invalidUserId = 40;
 
-        final Uri inValidUserPickerUri = getPickerUriForId(/* id */ 1, invalidUserId);
+        final Uri inValidUserPickerUri = getPickerUriForId(/* id */ 1, invalidUserId,
+                ACTION_PICK_IMAGES);
         updateReadUriPermission(inValidUserPickerUri, /* grant */ true);
 
         // This method is called on current context when pickerUriResolver wants to get the content
@@ -302,6 +351,8 @@
 
     @Test
     public void testPickerUriResolver_userValid() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
         updateReadUriPermission(sTestPickerUri, /* grant */ true);
 
         assertThat(PickerUriResolver.getUserId(sTestPickerUri)).isEqualTo(TEST_USER);
@@ -312,7 +363,102 @@
     }
 
     @Test
+    public void testPickerUriResolver_pickerUri_fileOpenWithRequireOriginal() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
+        // Grants given on original uri
+        updateReadUriPermission(sTestPickerUri, /* grant */ true);
+        sTestPickerUri = MediaStore.setRequireOriginal(sTestPickerUri);
+
+        assertThat(PickerUriResolver.getUserId(sTestPickerUri)).isEqualTo(TEST_USER);
+        try (ParcelFileDescriptor pfd = sTestPickerUriResolver.openFile(sTestPickerUri,
+                "r", /* signal */ null,
+                LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                        0))) {
+            fail("Require original should not be supported for picker uri:" + sTestPickerUri);
+        } catch (UnsupportedOperationException expected) {
+            // expected
+        }
+        try (ParcelFileDescriptor pfd = sTestPickerUriResolver.openFile(sTestPickerUri,
+                "r", /* signal */ null,
+                LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                        0))) {
+            fail("Require original should not be supported for picker uri:" + sTestPickerUri);
+        } catch (UnsupportedOperationException expected) {
+            // expected
+        }
+
+        try (AssetFileDescriptor afd = sTestPickerUriResolver.openTypedAssetFile(sTestPickerUri,
+                "image/*", /* opts */ null, /* signal */ null,
+                LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                        0))) {
+            fail("Require original should not be supported for picker uri:" + sTestPickerUri);
+        } catch (UnsupportedOperationException expected) {
+            // expected
+        }
+        try (AssetFileDescriptor afd = sTestPickerUriResolver.openTypedAssetFile(sTestPickerUri,
+                "image/*", /* opts */ null, /* signal */ null,
+                LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                        0))) {
+            fail("Require original should not be supported for picker uri:" + sTestPickerUri);
+        } catch (UnsupportedOperationException expected) {
+            // expected
+        }
+
+        testQuery(sTestPickerUri);
+        testGetType(sTestPickerUri, "image/jpeg");
+    }
+
+    @Test
+    public void testPickerUriResolver_pickerGetContentUri_fileOpenWithRequireOriginal()
+            throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_GET_CONTENT);
+        // Grants given on original uri
+        updateReadUriPermission(sTestPickerUri, /* grant */ true);
+        sTestPickerUri = MediaStore.setRequireOriginal(sTestPickerUri);
+
+        assertThat(PickerUriResolver.getUserId(sTestPickerUri)).isEqualTo(TEST_USER);
+        try (ParcelFileDescriptor pfd = sTestPickerUriResolver.openFile(sTestPickerUri,
+                "r", /* signal */ null,
+                LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                        ~LocalCallingIdentity.PERMISSION_IS_REDACTION_NEEDED))) {
+            assertThat(pfd).isNotNull();
+        }
+        try (ParcelFileDescriptor pfd = sTestPickerUriResolver.openFile(sTestPickerUri,
+                "r", /* signal */ null,
+                LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                        LocalCallingIdentity.PERMISSION_IS_REDACTION_NEEDED))) {
+            fail("Require original should not be supported when calling package does not have "
+                    + "required permission");
+        } catch (UnsupportedOperationException expected) {
+            // expected
+        }
+
+        try (AssetFileDescriptor afd = sTestPickerUriResolver.openTypedAssetFile(sTestPickerUri,
+                "image/*", /* opts */ null, /* signal */ null,
+                LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                        ~LocalCallingIdentity.PERMISSION_IS_REDACTION_NEEDED))) {
+            assertThat(afd).isNotNull();
+        }
+        try (AssetFileDescriptor afd = sTestPickerUriResolver.openTypedAssetFile(sTestPickerUri,
+                "image/*", /* opts */ null, /* signal */ null,
+                LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                        LocalCallingIdentity.PERMISSION_IS_REDACTION_NEEDED))) {
+            fail("Require original should not be supported when calling package does not have "
+                    + "required permission");
+        } catch (UnsupportedOperationException expected) {
+            // expected
+        }
+
+        testQuery(sTestPickerUri);
+        testGetType(sTestPickerUri, "image/jpeg");
+    }
+
+    @Test
     public void testQueryUnknownColumn() throws Exception {
+        sTestPickerUri = getPickerUriForId(ContentUris.parseId(sMediaStoreUriInOtherContext),
+                TEST_USER, ACTION_PICK_IMAGES);
         final int myUid = Process.myUid();
         final int myPid = Process.myPid();
         final String myPackageName = getContext().getPackageName();
@@ -367,25 +513,28 @@
                 Intent.FLAG_GRANT_READ_URI_PERMISSION)).thenReturn(permission);
     }
 
-    private static Uri getPickerUriForId(long id, int user) {
+    private static Uri getPickerUriForId(long id, int user, String action) {
         final Uri providerUri = PickerUriResolver
                 .getMediaUri(PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY)
                 .buildUpon()
                 .appendPath(String.valueOf(id))
                 .build();
-        return PickerUriResolver.wrapProviderUri(providerUri, user);
+        return PickerUriResolver.wrapProviderUri(providerUri, action, user);
     }
 
     private void testOpenFile(Uri uri) throws Exception {
         try (ParcelFileDescriptor pfd = sTestPickerUriResolver.openFile(uri, "r", /* signal */ null,
-                /* callingPid */ -1, /* callingUid */ -1)) {
+                LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                        0))) {
             assertThat(pfd).isNotNull();
         }
     }
 
     private void testOpenTypedAssetFile(Uri uri) throws Exception {
         try (AssetFileDescriptor afd = sTestPickerUriResolver.openTypedAssetFile(uri, "image/*",
-                /* opts */ null, /* signal */ null, /* callingPid */ -1, /* callingUid */ -1)) {
+                /* opts */ null, /* signal */ null,
+                LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                        0))) {
             assertThat(afd).isNotNull();
         }
     }
@@ -409,8 +558,9 @@
 
     private void testOpenFileInvalidUser(Uri uri) {
         try {
-            sTestPickerUriResolver.openFile(uri, "r", /* signal */ null, /* callingPid */ -1,
-                    /* callingUid */ -1);
+            sTestPickerUriResolver.openFile(uri, "r", /* signal */ null,
+                    LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                            0));
             fail("Invalid user specified in the picker uri: " + uri);
         } catch (FileNotFoundException expected) {
             // expected
@@ -421,7 +571,9 @@
     private void testOpenTypedAssetFileInvalidUser(Uri uri) throws Exception {
         try {
             sTestPickerUriResolver.openTypedAssetFile(uri, "image/*", /* opts */ null,
-                    /* signal */ null, /* callingPid */ -1, /* callingUid */ -1);
+                    /* signal */ null,
+                    LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                            0));
             fail("Invalid user specified in the picker uri: " + uri);
         } catch (FileNotFoundException expected) {
             // expected
@@ -449,8 +601,9 @@
 
     private void testOpenFile_permissionDenied(Uri uri) throws Exception {
         try {
-            sTestPickerUriResolver.openFile(uri, "r", /* signal */ null, /* callingPid */ -1,
-                    /* callingUid */ -1);
+            sTestPickerUriResolver.openFile(uri, "r", /* signal */ null,
+                    LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                            0));
             fail("openFile should fail if the caller does not have permission grant on the picker"
                     + " uri: " + uri);
         } catch (SecurityException expected) {
@@ -463,7 +616,9 @@
     private void testOpenTypedAssetFile_permissionDenied(Uri uri) throws Exception {
         try {
             sTestPickerUriResolver.openTypedAssetFile(uri, "image/*", /* opts */ null,
-                    /* signal */ null, /* callingPid */ -1, /* callingUid */ -1);
+                    /* signal */ null,
+                    LocalCallingIdentity.forTest(sCurrentContext, /* uid */ -1, /* permission */
+                            0));
             fail("openTypedAssetFile should fail if the caller does not have permission grant on"
                     + " the picker uri: " + uri);
         } catch (SecurityException expected) {
diff --git a/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java b/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java
index 7408e4b..f9ff002 100644
--- a/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java
+++ b/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.providers.media.photopicker;
 
 import static com.android.providers.media.PickerProviderMediaGenerator.MediaGenerator;
+import static com.android.providers.media.PickerUriResolver.INIT_PATH;
 import static com.android.providers.media.PickerUriResolver.REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI;
 import static com.android.providers.media.photopicker.NotificationContentObserver.MEDIA;
 
@@ -36,6 +37,7 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.database.Cursor;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.Process;
 import android.os.storage.StorageManager;
@@ -1725,7 +1727,7 @@
 
             // Simulate a UI session begins listening.
             contentResolver.registerContentObserver(REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI,
-                    /* notifyForDescendants */ false, refreshUiNotificationObserver);
+                    /* notifyForDescendants */ true, refreshUiNotificationObserver);
 
             mCloudPrimaryMediaGenerator.setMediaCollectionId(COLLECTION_2);
 
@@ -1733,6 +1735,11 @@
 
             assertWithMessage("Refresh ui notification should have been received.")
                     .that(refreshUiNotificationObserver.mNotificationReceived).isTrue();
+
+            assertWithMessage("Refresh ui notification uri should not include init path.")
+                    .that(refreshUiNotificationObserver.mNotificationUri.getLastPathSegment()
+                            .equals(INIT_PATH))
+                    .isFalse();
         } finally {
             contentResolver.unregisterContentObserver(refreshUiNotificationObserver);
         }
@@ -1744,7 +1751,7 @@
         final TestContentObserver refreshUiNotificationObserver = new TestContentObserver(null);
         try {
             contentResolver.registerContentObserver(REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI,
-                    /* notifyForDescendants */ false, refreshUiNotificationObserver);
+                    /* notifyForDescendants */ true, refreshUiNotificationObserver);
 
             assertWithMessage("Refresh ui notification should have not been received.")
                     .that(refreshUiNotificationObserver.mNotificationReceived).isFalse();
@@ -1760,7 +1767,12 @@
                     "Failed to receive refresh ui notification on change in cloud provider.")
                     .that(refreshUiNotificationObserver.mNotificationReceived).isTrue();
 
-            refreshUiNotificationObserver.mNotificationReceived = false;
+            assertWithMessage("Refresh ui notification uri should not include init path.")
+                    .that(refreshUiNotificationObserver.mNotificationUri.getLastPathSegment()
+                            .equals(INIT_PATH))
+                    .isTrue();
+
+            refreshUiNotificationObserver.clear();
 
             // The SET_CLOUD_PROVIDER is called using a different cloud provider from before
             mController.setCloudProvider(CLOUD_SECONDARY_PROVIDER_AUTHORITY);
@@ -1769,7 +1781,12 @@
                     "Failed to receive refresh ui notification on change in cloud provider.")
                     .that(refreshUiNotificationObserver.mNotificationReceived).isTrue();
 
-            refreshUiNotificationObserver.mNotificationReceived = false;
+            assertWithMessage("Refresh ui notification uri should not include init path.")
+                    .that(refreshUiNotificationObserver.mNotificationUri.getLastPathSegment()
+                            .equals(INIT_PATH))
+                    .isTrue();
+
+            refreshUiNotificationObserver.clear();
 
             // The cloud provider remains unchanged on PickerSyncController construction
             mController = PickerSyncController
@@ -1879,14 +1896,21 @@
 
     private static class TestContentObserver extends ContentObserver {
         boolean mNotificationReceived;
+        Uri mNotificationUri;
 
         TestContentObserver(Handler handler) {
             super(handler);
         }
 
         @Override
-        public void onChange(boolean selfChange) {
+        public void onChange(boolean selfChange, Uri uri) {
             mNotificationReceived = true;
+            mNotificationUri = uri;
+        }
+
+        public void clear() {
+            mNotificationReceived = false;
+            mNotificationUri = null;
         }
     }
 }
diff --git a/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java b/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java
index d7838af..769223d 100644
--- a/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java
@@ -21,7 +21,6 @@
 
 import static com.android.providers.media.util.MimeUtils.getExtensionFromMimeType;
 
-import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
@@ -41,6 +40,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.providers.media.PickerUriResolver;
 import com.android.providers.media.ProjectionHelper;
 import com.android.providers.media.photopicker.sync.PickerSyncLockManager;
 import com.android.providers.media.photopicker.sync.SyncTracker;
@@ -1351,20 +1351,38 @@
         // Assert all projection columns
         final String[] allProjection = mProjectionHelper.getProjectionMap(
                 PickerMediaColumns.class).keySet().toArray(new String[0]);
-        try (Cursor cr = mFacade.queryMediaIdForApps(LOCAL_PROVIDER, LOCAL_ID,
-                allProjection)) {
-            assertThat(cr.getCount()).isEqualTo(1);
+        try (Cursor cr = mFacade.queryMediaIdForApps(PickerUriResolver.PICKER_SEGMENT,
+                LOCAL_PROVIDER, LOCAL_ID, allProjection)) {
+            assertWithMessage(
+                    "Unexpected number of rows when asserting all projection columns with "
+                            + "PickerUriResolver as PICKER_SEGMENT on local provider.")
+                    .that(cr.getCount()).isEqualTo(1);
 
             cr.moveToFirst();
-            assertMediaStoreCursor(cr, LOCAL_ID, DATE_TAKEN_MS);
+            assertMediaStoreCursor(cr, LOCAL_ID, DATE_TAKEN_MS, PickerUriResolver.PICKER_SEGMENT);
+        }
+
+        try (Cursor cr = mFacade.queryMediaIdForApps(PickerUriResolver.PICKER_GET_CONTENT_SEGMENT,
+                LOCAL_PROVIDER, LOCAL_ID, allProjection)) {
+            assertWithMessage(
+                    "Unexpected number of rows when asserting all projection columns with "
+                            + "PickerUriResolver as PICKER_GET_CONTENT_SEGMENT on local provider.")
+                    .that(cr.getCount()).isEqualTo(1);
+
+            cr.moveToFirst();
+            assertMediaStoreCursor(cr, LOCAL_ID, DATE_TAKEN_MS,
+                    PickerUriResolver.PICKER_GET_CONTENT_SEGMENT);
         }
 
         // Assert one projection column
         final String[] oneProjection = new String[]{PickerMediaColumns.DATE_TAKEN};
 
-        try (Cursor cr = mFacade.queryMediaIdForApps(CLOUD_PROVIDER, CLOUD_ID,
-                oneProjection)) {
-            assertThat(cr.getCount()).isEqualTo(1);
+        try (Cursor cr = mFacade.queryMediaIdForApps(PickerUriResolver.PICKER_SEGMENT,
+                CLOUD_PROVIDER, CLOUD_ID, oneProjection)) {
+            assertWithMessage(
+                    "Unexpected number of rows when asserting one projection column with cloud "
+                            + "provider.")
+                    .that(cr.getCount()).isEqualTo(1);
 
             cr.moveToFirst();
             assertWithMessage(
@@ -1380,9 +1398,12 @@
                 invalidColumn
         };
 
-        try (Cursor cr = mFacade.queryMediaIdForApps(CLOUD_PROVIDER, CLOUD_ID,
-                invalidProjection)) {
-            assertThat(cr.getCount()).isEqualTo(1);
+        try (Cursor cr = mFacade.queryMediaIdForApps(PickerUriResolver.PICKER_SEGMENT,
+                CLOUD_PROVIDER, CLOUD_ID, invalidProjection)) {
+            assertWithMessage(
+                    "Unexpected number of rows when asserting invalid projection column with "
+                            + "cloud provider.")
+                    .that(cr.getCount()).isEqualTo(1);
 
             cr.moveToFirst();
             assertWithMessage(
@@ -2243,8 +2264,8 @@
         return mediaId + getExtensionFromMimeType(mimeType);
     }
 
-    private static String getData(String authority, String displayName) {
-        return "/sdcard/.transforms/synthetic/picker/0/" + authority + "/media/"
+    private static String getData(String authority, String displayName, String pickerSegmentType) {
+        return "/sdcard/.transforms/synthetic/" + pickerSegmentType + "/0/" + authority + "/media/"
                 + displayName;
     }
 
@@ -2270,8 +2291,10 @@
 
     private static void assertCloudMediaCursor(Cursor cursor, String id, String mimeType) {
         final String displayName = getDisplayName(id, mimeType);
-        final String localData = getData(LOCAL_PROVIDER, displayName);
-        final String cloudData = getData(CLOUD_PROVIDER, displayName);
+        final String localData = getData(LOCAL_PROVIDER, displayName,
+                PickerUriResolver.PICKER_SEGMENT);
+        final String cloudData = getData(CLOUD_PROVIDER, displayName,
+                PickerUriResolver.PICKER_SEGMENT);
 
         assertWithMessage("Unexpected value of MediaColumns.ID for the cloud media cursor.")
                 .that(cursor.getString(cursor.getColumnIndex(MediaColumns.ID)))
@@ -2358,10 +2381,11 @@
         }
     }
 
-    private static void assertMediaStoreCursor(Cursor cursor, String id, long dateTakenMs) {
+    private static void assertMediaStoreCursor(Cursor cursor, String id, long dateTakenMs,
+            String pickerSegmentType) {
         final String displayName = getDisplayName(id, MP4_VIDEO_MIME_TYPE);
-        final String localData = getData(LOCAL_PROVIDER, displayName);
-        final String cloudData = getData(CLOUD_PROVIDER, displayName);
+        final String localData = getData(LOCAL_PROVIDER, displayName, pickerSegmentType);
+        final String cloudData = getData(CLOUD_PROVIDER, displayName, pickerSegmentType);
 
         assertWithMessage(
                 "Unexpected value for PickerMediaColumns.DISPLAY_NAME for the media store cursor.")
diff --git a/tests/src/com/android/providers/media/photopicker/data/PickerResultTest.java b/tests/src/com/android/providers/media/photopicker/data/PickerResultTest.java
index e4ded70..3fb365d 100644
--- a/tests/src/com/android/providers/media/photopicker/data/PickerResultTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/PickerResultTest.java
@@ -16,9 +16,12 @@
 
 package com.android.providers.media.photopicker.data;
 
+import static android.content.Intent.ACTION_GET_CONTENT;
+import static android.provider.MediaStore.ACTION_PICK_IMAGES;
 import static android.provider.MediaStore.Files.FileColumns._SPECIAL_FORMAT_NONE;
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.providers.media.PickerUriResolver.PICKER_GET_CONTENT_SEGMENT;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -58,7 +61,7 @@
     }
 
     /**
-     * Tests {@link PickerResult#getPickerResponseIntent(boolean, List)} with single item
+     * Tests {@link PickerResult#getPickerResponseIntent(String, boolean, List)} with single item
      * @throws Exception
      */
     @Test
@@ -66,9 +69,10 @@
         List<Item> items = null;
         try {
             items = createItemSelection(1);
-            final Uri expectedPickerUri = PickerResult.getPickerUri(items.get(0).getContentUri());
+            final Uri expectedPickerUri = PickerResult.getPickerUri(ACTION_PICK_IMAGES,
+                    items.get(0).getContentUri());
             final Intent intent = PickerResult.getPickerResponseIntent(
-                    /* canSelectMultiple */ false, items);
+                    ACTION_PICK_IMAGES, /* canSelectMultiple */ false, items);
 
             final Uri result = intent.getData();
             assertPickerUriFormat(result);
@@ -84,8 +88,32 @@
         }
     }
 
+    @Test
+    public void testGetResultSingleForActionGetContent() throws Exception {
+        List<Item> items = null;
+        try {
+            items = createItemSelection(1);
+            final Uri expectedPickerUri = PickerResult.getPickerUri(ACTION_GET_CONTENT,
+                    items.get(0).getContentUri());
+            final Intent intent = PickerResult.getPickerResponseIntent(
+                    ACTION_GET_CONTENT, /* canSelectMultiple */ false, items);
+
+            final Uri result = intent.getData();
+            assertGetContentPickerUriFormat(result);
+            assertThat(result).isEqualTo(expectedPickerUri);
+
+            final ClipData clipData = intent.getClipData();
+            assertThat(clipData).isNotNull();
+            final int count = clipData.getItemCount();
+            assertThat(count).isEqualTo(1);
+            assertThat(clipData.getItemAt(0).getUri()).isEqualTo(expectedPickerUri);
+        } finally {
+            deleteFiles(items);
+        }
+    }
+
     /**
-     * Tests {@link PickerResult#getPickerResponseIntent(boolean, List)} with multiple items
+     * Tests {@link PickerResult#getPickerResponseIntent(String, boolean, List)} with multiple items
      * @throws Exception
      */
     @Test
@@ -95,11 +123,12 @@
             final int itemCount = 3;
             items = createItemSelection(itemCount);
             List<Uri> expectedPickerUris = new ArrayList<>();
-            for (Item item: items) {
-                expectedPickerUris.add(PickerResult.getPickerUri(item.getContentUri()));
+            for (Item item : items) {
+                expectedPickerUris.add(PickerResult.getPickerUri(ACTION_PICK_IMAGES,
+                        item.getContentUri()));
             }
-            final Intent intent = PickerResult.getPickerResponseIntent(/* canSelectMultiple */ true,
-                    items);
+            final Intent intent = PickerResult.getPickerResponseIntent(
+                    ACTION_PICK_IMAGES, /* canSelectMultiple */ true, items);
 
             final ClipData clipData = intent.getClipData();
             final int count = clipData.getItemCount();
@@ -114,10 +143,36 @@
         }
     }
 
+    @Test
+    public void testGetResultMultipleForActionGetContent() throws Exception {
+        ArrayList<Item> items = null;
+        try {
+            final int itemCount = 3;
+            items = createItemSelection(itemCount);
+            List<Uri> expectedPickerUris = new ArrayList<>();
+            for (Item item : items) {
+                expectedPickerUris.add(PickerResult.getPickerUri(ACTION_GET_CONTENT,
+                        item.getContentUri()));
+            }
+            final Intent intent = PickerResult.getPickerResponseIntent(
+                    ACTION_GET_CONTENT, /* canSelectMultiple */ true, items);
+
+            final ClipData clipData = intent.getClipData();
+            final int count = clipData.getItemCount();
+            assertThat(count).isEqualTo(itemCount);
+            for (int i = 0; i < count; i++) {
+                Uri uri = clipData.getItemAt(i).getUri();
+                assertGetContentPickerUriFormat(uri);
+                assertThat(uri).isEqualTo(expectedPickerUris.get(i));
+            }
+        } finally {
+            deleteFiles(items);
+        }
+    }
+
     /**
-     * Tests {@link PickerResult#getPickerResponseIntent(boolean, List)} when the user selected
-     * only one item in multi-select mode
-     * @throws Exception
+     * Tests {@link PickerResult#getPickerResponseIntent(String, boolean, List)} when the user
+     * selected only one item in multi-select mode
      */
     @Test
     public void testGetResultMultiple_onlyOneItemSelected() throws Exception {
@@ -125,9 +180,10 @@
         try {
             final int itemCount = 1;
             items = createItemSelection(itemCount);
-            final Uri expectedPickerUri = PickerResult.getPickerUri(items.get(0).getContentUri());
-            final Intent intent = PickerResult.getPickerResponseIntent(/* canSelectMultiple */ true,
-                    items);
+            final Uri expectedPickerUri = PickerResult.getPickerUri(ACTION_PICK_IMAGES,
+                    items.get(0).getContentUri());
+            final Intent intent = PickerResult.getPickerResponseIntent(
+                    ACTION_PICK_IMAGES, /* canSelectMultiple */ true, items);
 
             final ClipData clipData = intent.getClipData();
             final int count = clipData.getItemCount();
@@ -144,6 +200,12 @@
         assertThat(uri.toString().startsWith(pickerUriPrefix)).isTrue();
     }
 
+    private void assertGetContentPickerUriFormat(Uri uri) {
+        final String pickerNonRedactedUriPrefix = MediaStore.AUTHORITY_URI.buildUpon().appendPath(
+                PICKER_GET_CONTENT_SEGMENT).build().toString();
+        assertThat(uri.toString().startsWith(pickerNonRedactedUriPrefix)).isTrue();
+    }
+
     /**
      * Returns a PhotoSelection on which the test app does not have access to.
      */
diff --git a/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java b/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java
index 4ca8dd9..4ca0e7b 100644
--- a/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java
+++ b/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java
@@ -76,6 +76,7 @@
 import com.android.providers.media.photopicker.data.model.Category;
 import com.android.providers.media.photopicker.data.model.Item;
 import com.android.providers.media.photopicker.data.model.ModelTestUtils;
+import com.android.providers.media.photopicker.data.model.RefreshRequest;
 import com.android.providers.media.photopicker.data.model.UserId;
 
 import org.junit.Before;
@@ -598,6 +599,53 @@
     }
 
     @Test
+    public void testParseValuesFromPickImagesIntent_launchPickerInPhotosTab() {
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB, MediaStore.PICK_IMAGES_TAB_IMAGES);
+
+        mPickerViewModel.parseValuesFromIntent(intent);
+
+        assertThat(mPickerViewModel.getPickerLaunchTab()).isEqualTo(
+                MediaStore.PICK_IMAGES_TAB_IMAGES);
+    }
+
+    @Test
+    public void testParseValuesFromPickImagesIntent_launchPickerInAlbumsTab() {
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB, MediaStore.PICK_IMAGES_TAB_ALBUMS);
+
+        mPickerViewModel.parseValuesFromIntent(intent);
+
+        assertThat(mPickerViewModel.getPickerLaunchTab()).isEqualTo(
+                MediaStore.PICK_IMAGES_TAB_ALBUMS);
+    }
+
+    @Test
+    public void testParseValuesFromPickImagesIntent_launchPickerWithIncorrectTabOption() {
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB, 2);
+
+        try {
+            mPickerViewModel.parseValuesFromIntent(intent);
+            fail("Incorrect value passed for the picker launch tab option in the intent");
+        } catch (IllegalArgumentException expected) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testParseValuesFromGetContentIntent_extraPickerLaunchTab() {
+        final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB, MediaStore.PICK_IMAGES_TAB_ALBUMS);
+
+        mPickerViewModel.parseValuesFromIntent(intent);
+
+        // GET_CONTENT doesn't support this option. Launch tab will always default to photos
+        assertThat(mPickerViewModel.getPickerLaunchTab()).isEqualTo(
+                MediaStore.PICK_IMAGES_TAB_IMAGES);
+    }
+
+    @Test
     public void testShouldShowOnlyLocalFeatures() {
         mConfigStore.enableCloudMediaFeature();
 
@@ -618,17 +666,17 @@
 
     @Test
     public void testRefreshUiNotifications() throws InterruptedException {
-        final LiveData<Boolean> shouldRefreshUi = mPickerViewModel.shouldRefreshUiLiveData();
-        assertFalse(shouldRefreshUi.getValue());
+        final LiveData<RefreshRequest> shouldRefreshUi = mPickerViewModel.refreshUiLiveData();
+        assertFalse(shouldRefreshUi.getValue().shouldRefreshPicker());
 
         final ContentResolver contentResolver = sTargetContext.getContentResolver();
         contentResolver.notifyChange(REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI, null);
 
         TimeUnit.MILLISECONDS.sleep(100);
-        assertTrue(shouldRefreshUi.getValue());
+        assertTrue(shouldRefreshUi.getValue().shouldRefreshPicker());
 
-        mPickerViewModel.resetAllContentInCurrentProfile();
-        assertFalse(shouldRefreshUi.getValue());
+        mPickerViewModel.resetAllContentInCurrentProfile(false);
+        assertFalse(shouldRefreshUi.getValue().shouldRefreshPicker());
     }
 
     @Test
diff --git a/tests/src/com/android/providers/media/stableuris/job/StableUriIdleMaintenanceServiceTest.java b/tests/src/com/android/providers/media/stableuris/job/StableUriIdleMaintenanceServiceTest.java
index 3405ff0..0aee9df 100644
--- a/tests/src/com/android/providers/media/stableuris/job/StableUriIdleMaintenanceServiceTest.java
+++ b/tests/src/com/android/providers/media/stableuris/job/StableUriIdleMaintenanceServiceTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.providers.media.tests.utils.PublicVolumeSetupHelper.createNewPublicVolume;
 import static com.android.providers.media.tests.utils.PublicVolumeSetupHelper.deletePublicVolumes;
+import static com.android.providers.media.tests.utils.PublicVolumeSetupHelper.executeShellCommand;
 import static com.android.providers.media.util.FileUtils.getVolumePath;
 
 import static org.junit.Assert.assertEquals;
@@ -33,6 +34,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -45,6 +47,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.providers.media.ConfigStore;
+import com.android.providers.media.DatabaseBackupAndRecovery;
 import com.android.providers.media.stableuris.dao.BackupIdRow;
 
 import org.junit.AfterClass;
@@ -63,7 +66,6 @@
 import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 31, codeName = "S")
 public class StableUriIdleMaintenanceServiceTest {
     private static final String TAG = "StableUriIdleMaintenanceServiceTest";
 
@@ -86,6 +88,9 @@
     @BeforeClass
     public static void setUpClass() throws Exception {
         adoptShellPermission();
+        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
+            return;
+        }
 
         // Read existing value of the flag
         sInitialDeviceConfigValueForInternal = Boolean.parseBoolean(
@@ -110,6 +115,10 @@
 
     @AfterClass
     public static void tearDownClass() throws Exception {
+        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
+            dropShellPermission();
+            return;
+        }
 
         // Restore previous value of the flag
         DeviceConfig.setProperty(ConfigStore.NAMESPACE_MEDIAPROVIDER,
@@ -126,6 +135,7 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
     public void testDataMigrationForInternalVolume() throws Exception {
         final Context context = InstrumentationRegistry.getTargetContext();
         final ContentResolver resolver = context.getContentResolver();
@@ -163,6 +173,11 @@
     public void testDataMigrationForExternalVolume() throws Exception {
         final Context context = InstrumentationRegistry.getTargetContext();
         final ContentResolver resolver = context.getContentResolver();
+        // Enable feature for Android R
+        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
+            executeShellCommand(
+                    "setprop " + DatabaseBackupAndRecovery.STABLE_URI_EXTERNAL_PROPERTY + " true");
+        }
         Set<String> newFilePaths = new HashSet<String>();
         Map<String, Long> pathToIdMap = new HashMap<>();
         MediaStore.waitForIdle(resolver);
@@ -207,6 +222,12 @@
             for (String path : newFilePaths) {
                 new File(path).delete();
             }
+
+            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
+                executeShellCommand(
+                        "setprop " + DatabaseBackupAndRecovery.STABLE_URI_EXTERNAL_PROPERTY
+                          + " false");
+            }
         }
     }
 
diff --git a/tests/src/com/android/providers/media/util/IsoInterfaceTest.java b/tests/src/com/android/providers/media/util/IsoInterfaceTest.java
index 37dd48f..a105bbd 100644
--- a/tests/src/com/android/providers/media/util/IsoInterfaceTest.java
+++ b/tests/src/com/android/providers/media/util/IsoInterfaceTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
@@ -27,11 +28,14 @@
 
 import com.android.providers.media.R;
 
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 
@@ -97,6 +101,18 @@
         assertEquals("3F9DD7A46B26513A7C35272F0D623A06", xmp.getOriginalDocumentId());
     }
 
+
+    @Test
+    @Ignore // This test creates a file that causes MediaProvider to OOM with our current
+    // IsoInterface implementation.
+    // While MediaProvider should now be resistant to that, we cannot leave this test safely enabled
+    // in a test suite as for b/316578793
+    // Leaving its implementation here to test further improvement to IsoInterface implementation.
+    public void testFileWithTooManyBoxesDoesNotRunOutOfMemory() throws Exception {
+        final File file = createFileWithLotsOfBoxes("too-many-boxes");
+        assertThrows(IOException.class, () -> IsoInterface.fromFile(file));
+    }
+
     @Test
     public void testIsoMeta() throws Exception {
         final IsoInterface isoMeta = IsoInterface.fromFile(stageFile(R.raw.test_video_xmp));
@@ -128,4 +144,19 @@
         }
         return file;
     }
+
+    private static File createFileWithLotsOfBoxes(String filename) throws Exception {
+        File file = File.createTempFile(filename, ".mp4");
+        try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
+            byte[] sizeHeader = new byte[]{0x00, 0x00, 0x00, 0x08};
+            out.write(sizeHeader);
+            out.write("ftyp".getBytes());
+            byte[] freeBlock = "free".getBytes();
+            for (int i = 0; i < 5000000; i++) {
+                out.write(sizeHeader);
+                out.write(freeBlock);
+            }
+        }
+        return file;
+    }
 }
diff --git a/tests/src/com/android/providers/media/util/SyntheticPathUtilsTest.java b/tests/src/com/android/providers/media/util/SyntheticPathUtilsTest.java
index b913114..0c8c5df 100644
--- a/tests/src/com/android/providers/media/util/SyntheticPathUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/SyntheticPathUtilsTest.java
@@ -23,9 +23,13 @@
 import static com.android.providers.media.util.SyntheticPathUtils.isPickerPath;
 import static com.android.providers.media.util.SyntheticPathUtils.isRedactedPath;
 import static com.android.providers.media.util.SyntheticPathUtils.isSyntheticPath;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import androidx.test.runner.AndroidJUnit4;
+
+import com.android.providers.media.PickerUriResolver;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -46,7 +50,10 @@
 
     @Test
     public void testGetPickerRelativePath() throws Exception {
-        assertThat(getPickerRelativePath()).isEqualTo(".transforms/synthetic/picker");
+        assertThat(getPickerRelativePath(PickerUriResolver.PICKER_SEGMENT)).isEqualTo(
+                ".transforms/synthetic/picker");
+        assertThat(getPickerRelativePath(PickerUriResolver.PICKER_GET_CONTENT_SEGMENT)).isEqualTo(
+                ".transforms/synthetic/picker_get_content");
     }
 
     @Test
diff --git a/tools/photopicker/res/layout/activity_main.xml b/tools/photopicker/res/layout/activity_main.xml
index 6348a4e..d5db47d 100644
--- a/tools/photopicker/res/layout/activity_main.xml
+++ b/tools/photopicker/res/layout/activity_main.xml
@@ -100,6 +100,7 @@
             android:textSize="16sp" />
     </LinearLayout>
 
+
     <CheckBox
         android:id="@+id/cbx_ordered_selection"
         android:layout_width="wrap_content"
@@ -107,6 +108,37 @@
         android:text="ORDERED SELECTION"
         android:textSize="16sp" />
 
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <CheckBox
+            android:id="@+id/cbx_set_picker_launch_tab"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/picker_launch_tab_option"
+            android:textSize="16sp" />
+
+        <RadioGroup
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <RadioButton android:id="@+id/rb_albums"
+                         android:layout_width="wrap_content"
+                         android:layout_height="wrap_content"
+                         android:text="Albums"
+                         android:enabled="false"/>
+            <RadioButton android:id="@+id/rb_photos"
+                         android:layout_width="wrap_content"
+                         android:layout_height="wrap_content"
+                         android:text="Photos"
+                         android:enabled="false"/>
+        </RadioGroup>
+
+    </LinearLayout>
+
+
     <Button
         android:id="@+id/launch_button"
         android:layout_width="match_parent"
diff --git a/tools/photopicker/res/values/strings.xml b/tools/photopicker/res/values/strings.xml
new file mode 100644
index 0000000..b6b6131
--- /dev/null
+++ b/tools/photopicker/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+
+<resources>
+    <!-- Picker launch tab checkbox label -->
+    <string name="picker_launch_tab_option">SET PICKER LAUNCH TAB</string>
+</resources>
\ No newline at end of file
diff --git a/tools/photopicker/src/com/android/providers/media/tools/photopicker/PhotoPickerToolActivity.java b/tools/photopicker/src/com/android/providers/media/tools/photopicker/PhotoPickerToolActivity.java
index 1d549f3..98e58c0 100644
--- a/tools/photopicker/src/com/android/providers/media/tools/photopicker/PhotoPickerToolActivity.java
+++ b/tools/photopicker/src/com/android/providers/media/tools/photopicker/PhotoPickerToolActivity.java
@@ -34,6 +34,7 @@
 import android.widget.EditText;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.RadioButton;
 import android.widget.ScrollView;
 import android.widget.TextView;
 import android.widget.VideoView;
@@ -48,6 +49,8 @@
     private static final String TAG = "PhotoPickerToolActivity";
     private static final String EXTRA_PICK_IMAGES_MAX = "android.provider.extra.PICK_IMAGES_MAX";
     private static final String ACTION_PICK_IMAGES = "android.provider.action.PICK_IMAGES";
+    private static final String EXTRA_PICK_IMAGES_LAUNCH_TAB =
+            "android.provider.extra.PICK_IMAGES_LAUNCH_TAB";
     private static final int PICK_IMAGES_MAX_LIMIT = 100;
     private static final int REQUEST_CODE = 42;
 
@@ -63,10 +66,17 @@
     private CheckBox mSetSelectionCountCheckBox;
     private CheckBox mAllowMultipleCheckBox;
     private CheckBox mGetContentCheckBox;
+
     private CheckBox mOrderedSelectionCheckBox;
+
+    private CheckBox mPickerLaunchTabCheckBox;
+
     private EditText mMaxCountText;
     private EditText mMimeTypeText;
 
+    private RadioButton mAlbumsRadioButton;
+    private RadioButton mPhotosRadioButton;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -82,12 +92,17 @@
         mMaxCountText = findViewById(R.id.edittext_max_count);
         mMimeTypeText = findViewById(R.id.edittext_mime_type);
         mScrollView = findViewById(R.id.scrollview);
+        mPickerLaunchTabCheckBox = findViewById(R.id.cbx_set_picker_launch_tab);
+        mAlbumsRadioButton = findViewById(R.id.rb_albums);
+        mPhotosRadioButton = findViewById(R.id.rb_photos);
 
         mSetImageOnlyCheckBox.setOnCheckedChangeListener(this::onShowImageOnlyCheckedChanged);
         mSetVideoOnlyCheckBox.setOnCheckedChangeListener(this::onShowVideoOnlyCheckedChanged);
         mSetMimeTypeCheckBox.setOnCheckedChangeListener(this::onSetMimeTypeCheckedChanged);
         mSetSelectionCountCheckBox.setOnCheckedChangeListener(
                 this::onSetSelectionCountCheckedChanged);
+        mPickerLaunchTabCheckBox.setOnCheckedChangeListener(
+                this::onSetPickerLaunchTabCheckedChanged);
 
         mMaxCountText.addTextChangedListener(new TextWatcher() {
             @Override
@@ -157,6 +172,11 @@
         mMaxCountText.setEnabled(isChecked);
     }
 
+    private void onSetPickerLaunchTabCheckedChanged(View view, boolean isChecked) {
+        mAlbumsRadioButton.setEnabled(isChecked);
+        mPhotosRadioButton.setEnabled(isChecked);
+    }
+
     private void onLaunchButtonClicked(View view) {
         final Intent intent;
         if (mGetContentCheckBox.isChecked()) {
@@ -164,6 +184,16 @@
             intent.setType("*/*");
         } else {
             intent = new Intent(ACTION_PICK_IMAGES);
+            // This extra is not permitted in GET_CONTENT
+            if (mPickerLaunchTabCheckBox.isChecked()) {
+                int launchTab;
+                if (mAlbumsRadioButton.isChecked()) {
+                    launchTab = 0;
+                } else {
+                    launchTab = 1;
+                }
+                intent.putExtra(EXTRA_PICK_IMAGES_LAUNCH_TAB, launchTab);
+            }
         }
 
         if (mAllowMultipleCheckBox.isChecked()) {