Merge "Integrate PhotoPickeractivity to use PickerResult"
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index b5dbd9b..a1f2a0d 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -421,9 +421,21 @@
         return mVolumeCache.getVolumeId(file);
     }
 
-    public @NonNull Collection<File> getVolumeScanPaths(String volumeName)
+    private @NonNull Collection<File> getAllowedVolumePaths(String volumeName)
             throws FileNotFoundException {
-        return mVolumeCache.getVolumeScanPaths(volumeName, mCallingIdentity.get().getUser());
+        // This method is used to verify whether a path belongs to a certain volume name;
+        // we can't always use the calling user's identity here to determine exactly which
+        // volume is meant, because the MediaScanner may scan paths belonging to another user,
+        // eg a clone user.
+        // So, for volumes like external_primary, just return allowed paths for all users.
+        List<UserHandle> users = mUserCache.getUsersCached();
+        ArrayList<File> allowedPaths = new ArrayList<>();
+        for (UserHandle user : users) {
+            Collection<File> volumeScanPaths = mVolumeCache.getVolumeScanPaths(volumeName, user);
+            allowedPaths.addAll(volumeScanPaths);
+        }
+
+        return allowedPaths;
     }
 
     /**
@@ -3476,7 +3488,7 @@
         final String volumeName = resolveVolumeName(uri);
         try {
             // Quick check that the requested path actually lives on volume
-            final Collection<File> allowed = getVolumeScanPaths(volumeName);
+            final Collection<File> allowed = getAllowedVolumePaths(volumeName);
             final File actual = new File(values.getAsString(MediaColumns.DATA))
                     .getCanonicalFile();
             if (!FileUtils.contains(allowed, actual)) {
diff --git a/src/com/android/providers/media/TranscodeHelper.java b/src/com/android/providers/media/TranscodeHelper.java
index e1ffbca..9a74ddc 100644
--- a/src/com/android/providers/media/TranscodeHelper.java
+++ b/src/com/android/providers/media/TranscodeHelper.java
@@ -437,8 +437,8 @@
         CountDownLatch latch = null;
         long startTime = SystemClock.elapsedRealtime();
         boolean result = false;
-        int errorCode = TranscodingSession.ERROR_NONE;
-        int failureReason = TRANSCODING_DATA__FAILURE_CAUSE__CAUSE_UNKNOWN;
+        int errorCode = TranscodingSession.ERROR_SERVICE_DIED;
+        int failureReason = TRANSCODING_DATA__FAILURE_CAUSE__TRANSCODING_SERVICE_ERROR;
 
         try {
             synchronized (mLock) {
@@ -481,6 +481,16 @@
                 transcodingSession.cancel();
             }
         } finally {
+            if (storageSession == null) {
+                Log.w(TAG, "Failed to create a StorageTranscodingSession");
+                // We were unable to even queue the request. Which means the media service is
+                // in a very bad state
+                reportTranscodingResult(uid, result, errorCode, failureReason,
+                        SystemClock.elapsedRealtime() - startTime, reason,
+                        src, dst, false /* hasAnr */);
+                return false;
+            }
+
             storageSession.notifyFinished(failureReason, errorCode);
             if (errorCode == TranscodingSession.ERROR_DROPPED_BY_SERVICE) {
                 // If the transcoding service drops a request for a uid the uid will be denied
diff --git a/src/com/android/providers/media/photopicker/data/LocalItemsProvider.java b/src/com/android/providers/media/photopicker/data/LocalItemsProvider.java
index 3e1945f..ed7f770 100644
--- a/src/com/android/providers/media/photopicker/data/LocalItemsProvider.java
+++ b/src/com/android/providers/media/photopicker/data/LocalItemsProvider.java
@@ -73,9 +73,8 @@
      * @param category the category of items to return, {@link Category.CategoryType} are supported.
      *                 {@code null} defaults to {@link Category#CATEGORY_DEFAULT} which returns
      *                 items from all categories.
-     * @param offset the offset after which to return items. Does not respect non-positive
-     *               values.
-     * @param limit the limit of items to return. Does not respect non-positive values.
+     * @param offset the offset after which to return items.
+     * @param limit the limit of number of items to return.
      * @param mimeType the mime type of item, only {@code image/*} or {@code video/*} is an
      *                 acceptable mimeType here. Any other mimeType than image/video throws error.
      *                 {@code null} returns all images/videos that are scanned by
@@ -202,12 +201,9 @@
             extras.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs);
             extras.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER,
                     MediaColumns.DATE_TAKEN + " DESC");
-            if (offset > 0) {
-                extras.putInt(ContentResolver.QUERY_ARG_OFFSET, offset);
-            }
-            if (limit > 0) {
-                extras.putString(ContentResolver.QUERY_ARG_LIMIT, String.valueOf(limit));
-            }
+            extras.putInt(ContentResolver.QUERY_ARG_OFFSET, offset);
+            extras.putString(ContentResolver.QUERY_ARG_LIMIT, String.valueOf(limit));
+
             return client.query(contentUri, projection, extras, null);
         } catch (RemoteException ignored) {
             // Do nothing, return null.
diff --git a/src/com/android/providers/media/photopicker/data/model/Item.java b/src/com/android/providers/media/photopicker/data/model/Item.java
index 4780027..f57c5bb 100644
--- a/src/com/android/providers/media/photopicker/data/model/Item.java
+++ b/src/com/android/providers/media/photopicker/data/model/Item.java
@@ -19,7 +19,6 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.MediaStore;
-import android.provider.MediaStore.Files.FileColumns;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -41,14 +40,16 @@
         public static String DISPLAY_NAME = MediaStore.MediaColumns.DISPLAY_NAME;
         public static String VOLUME_NAME = MediaStore.MediaColumns.VOLUME_NAME;
         public static String DATE_TAKEN = MediaStore.MediaColumns.DATE_TAKEN;
+        public static String DURATION = MediaStore.MediaColumns.DURATION;
         public static String USER_ID = MediaStore.Files.FileColumns._USER_ID;
 
         private static final String[] ALL_COLUMNS = {
-                ItemColumns.ID,
-                ItemColumns.MIME_TYPE,
-                ItemColumns.DISPLAY_NAME,
-                ItemColumns.VOLUME_NAME,
-                ItemColumns.DATE_TAKEN,
+                ID,
+                MIME_TYPE,
+                DISPLAY_NAME,
+                VOLUME_NAME,
+                DATE_TAKEN,
+                DURATION,
                 // TODO: Unable to query USER_ID
                 // ItemColumns.USER_ID,
         };
@@ -57,8 +58,9 @@
     }
 
     private long mId;
-    private long mDataTaken;
+    private long mDateTaken;
     private String mDisplayName;
+    private int mDuration;
     private String mMimeType;
     private String mVolumeName;
     private Uri mUri;
@@ -85,12 +87,16 @@
         return mDisplayName;
     }
 
+    public int getDuration() {
+        return mDuration;
+    }
+
     public String getMimeType() {
         return mMimeType;
     }
 
-    public long getDataTaken() {
-        return mDataTaken;
+    public long getDateTaken() {
+        return mDateTaken;
     }
 
     public String getVolumeName() {
@@ -111,8 +117,9 @@
         mId = getCursorLong(cursor, ItemColumns.ID);
         mMimeType = getCursorString(cursor, ItemColumns.MIME_TYPE);
         mDisplayName = getCursorString(cursor, ItemColumns.DISPLAY_NAME);
-        mDataTaken = getCursorLong(cursor, ItemColumns.DATE_TAKEN);
+        mDateTaken = getCursorLong(cursor, ItemColumns.DATE_TAKEN);
         mVolumeName = getCursorString(cursor, ItemColumns.VOLUME_NAME);
+        mDuration = getCursorInt(cursor, ItemColumns.DURATION);
 
         // TODO (b/188867567): Currently, we only has local data source,
         //  get the uri from provider
diff --git a/src/com/android/providers/media/util/UserCache.java b/src/com/android/providers/media/util/UserCache.java
index 885e07e..276e3f8 100644
--- a/src/com/android/providers/media/util/UserCache.java
+++ b/src/com/android/providers/media/util/UserCache.java
@@ -84,6 +84,12 @@
         }
     }
 
+    public @NonNull List<UserHandle> getUsersCached() {
+        synchronized (mLock) {
+            return (List<UserHandle>) mUsers.clone();
+        }
+    }
+
     public @NonNull Context getContextForUser(@NonNull UserHandle user) {
         Context userContext;
         synchronized (mLock) {
diff --git a/tests/src/com/android/providers/media/photopicker/LocalItemsProviderTest.java b/tests/src/com/android/providers/media/photopicker/LocalItemsProviderTest.java
index 4e1a577..84d3d10 100644
--- a/tests/src/com/android/providers/media/photopicker/LocalItemsProviderTest.java
+++ b/tests/src/com/android/providers/media/photopicker/LocalItemsProviderTest.java
@@ -304,7 +304,8 @@
      */
     @Test
     public void testGetItems() throws Exception {
-        Cursor res = sLocalItemsProvider.getItems(null, 0, 0, null);
+        Cursor res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0,
+                /* limit */ -1, /* mimeType */ null);
         assertThat(res).isNotNull();
         final int initialCountOfItems = res.getCount();
 
@@ -314,7 +315,8 @@
         File imageFile = assertCreateNewImage();
         File videoFile = assertCreateNewVideo();
         try {
-            res = sLocalItemsProvider.getItems(null, 0, 0, null);
+            res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0, /* limit */ -1,
+                    /* mimeType */ null);
             assertThat(res).isNotNull();
             final int laterCountOfItems = res.getCount();
 
@@ -336,7 +338,8 @@
      */
     @Test
     public void testGetItems_nonMedia() throws Exception {
-        Cursor res = sLocalItemsProvider.getItems(null, 0, 0, null);
+        Cursor res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0,
+                /* limit */ -1, /* mimeType */ null);
         assertThat(res).isNotNull();
         final int initialCountOfItems = res.getCount();
 
@@ -347,7 +350,8 @@
         File imageFileHidden = assertCreateNewImage(hiddenDir);
         File videoFileHidden = assertCreateNewVideo(hiddenDir);
         try {
-            res = sLocalItemsProvider.getItems(null, 0, 0, null);
+            res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0, /* limit */ -1,
+                    /* mimeType */ null);
             assertThat(res).isNotNull();
             final int laterCountOfItems = res.getCount();
 
@@ -367,7 +371,8 @@
      */
     @Test
     public void testGetItemsImages() throws Exception {
-        Cursor res = sLocalItemsProvider.getItems(null, 0, 0, "image/*");
+        Cursor res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0,
+                /* limit */ -1, /* mimeType */ "image/*");
         assertThat(res).isNotNull();
         final int initialCountOfItems = res.getCount();
 
@@ -377,7 +382,8 @@
         File imageFile = assertCreateNewImage();
         File videoFile = assertCreateNewVideo();
         try {
-            res = sLocalItemsProvider.getItems(null, 0, 0, "image/*");
+            res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0, /* limit */ -1,
+                    /* mimeType */ "image/*");
             assertThat(res).isNotNull();
             final int laterCountOfItems = res.getCount();
 
@@ -399,14 +405,16 @@
      */
     @Test
     public void testGetItemsImages_png() throws Exception {
-        Cursor res = sLocalItemsProvider.getItems(null, 0, 0, "image/png");
+        Cursor res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0,
+                /* limit */ -1, /* mimeType */ "image/png");
         assertThat(res).isNotNull();
         final int initialCountOfItems = res.getCount();
 
         // Create a jpg file image. Tests negative use case, this should not be returned below.
         File imageFile = assertCreateNewImage();
         try {
-            res = sLocalItemsProvider.getItems(null, 0, 0, "image/png");
+            res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0, /* limit */ -1,
+                    /* mimeType */ "image/png");
             assertThat(res).isNotNull();
             final int laterCountOfItems = res.getCount();
 
@@ -424,7 +432,8 @@
      */
     @Test
     public void testGetItemsImages_nonMedia() throws Exception {
-        Cursor res = sLocalItemsProvider.getItems(null, 0, 0, "image/*");
+        Cursor res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0,
+                /* limit */ -1, /* mimeType */ "image/*");
         assertThat(res).isNotNull();
         final int initialCountOfItems = res.getCount();
 
@@ -435,7 +444,8 @@
         File imageFileHidden = assertCreateNewImage(hiddenDir);
         File videoFileHidden = assertCreateNewVideo(hiddenDir);
         try {
-            res = sLocalItemsProvider.getItems(null, 0, 0, "image/*");
+            res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0, /* limit */ -1,
+                    /* mimeType */ "image/*");
             assertThat(res).isNotNull();
             final int laterCountOfItems = res.getCount();
 
@@ -455,7 +465,8 @@
      */
     @Test
     public void testGetItemsVideos() throws Exception {
-        Cursor res = sLocalItemsProvider.getItems(null, 0, 0, "video/*");
+        Cursor res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0,
+                /* limit */ -1,  /* mimeType */ "video/*");
         assertThat(res).isNotNull();
         final int initialCountOfItems = res.getCount();
 
@@ -465,7 +476,8 @@
         File imageFile = assertCreateNewImage();
         File videoFile = assertCreateNewVideo();
         try {
-            res = sLocalItemsProvider.getItems(null, 0, 0, "video/*");
+            res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0, /* limit */ -1,
+                    /* mimeType */ "video/*");
             assertThat(res).isNotNull();
             final int laterCountOfItems = res.getCount();
 
@@ -487,14 +499,16 @@
      */
     @Test
     public void testGetItemsVideos_mp4() throws Exception {
-        Cursor res = sLocalItemsProvider.getItems(null, 0, 0, "video/mp4");
+        Cursor res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0,
+                /* limit */ -1, /* mimeType */ "video/mp4");
         assertThat(res).isNotNull();
         final int initialCountOfItems = res.getCount();
 
         // Create a mp4 video file. Tests positive use case, this should be returned below.
         File videoFile = assertCreateNewVideo();
         try {
-            res = sLocalItemsProvider.getItems(null, 0, 0, "video/mp4");
+            res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0, /* limit */ -1,
+                    /* mimeType */ "video/mp4");
             assertThat(res).isNotNull();
             final int laterCountOfItems = res.getCount();
 
@@ -512,7 +526,8 @@
      */
     @Test
     public void testGetItemsVideos_nonMedia() throws Exception {
-        Cursor res = sLocalItemsProvider.getItems(null, 0, 0, "video/*");
+        Cursor res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0,
+                /* limit */ -1, /* mimeType */ "video/*");
         assertThat(res).isNotNull();
         final int initialCountOfItems = res.getCount();
 
@@ -522,7 +537,8 @@
         File imageFileHidden = assertCreateNewImage(hiddenDir);
         File videoFileHidden = assertCreateNewVideo(hiddenDir);
         try {
-            res = sLocalItemsProvider.getItems(null, 0, 0, "video/*");
+            res = sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0, /* limit */ -1,
+                    /* mimeType */ "video/*");
             assertThat(res).isNotNull();
             final int laterCountOfItems = res.getCount();
 
@@ -543,7 +559,8 @@
     @Test
     public void testGetItemsInvalidParam() throws Exception {
         try {
-            sLocalItemsProvider.getItems(null, 0, 0, "audio/*");
+            sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0, /* limit */ -1,
+                    /* mimeType */ "audio/*");
             fail("Expected IllegalArgumentException for audio mimeType");
         } catch (IllegalArgumentException expected) {
             // Expected flow
@@ -559,7 +576,8 @@
     @Test
     public void testGetItemsAllMimeType() throws Exception {
         try {
-            sLocalItemsProvider.getItems(null, 0, 0, "*/*");
+            sLocalItemsProvider.getItems(/* category */ null, /* offset */ 0, /* limit */ -1,
+                    /* mimeType */ "*/*");
             fail("Expected IllegalArgumentException for audio mimeType");
         } catch (IllegalArgumentException expected) {
             // Expected flow
diff --git a/tests/src/com/android/providers/media/photopicker/data/model/ItemTest.java b/tests/src/com/android/providers/media/photopicker/data/model/ItemTest.java
index 8df3889..f94aa67 100644
--- a/tests/src/com/android/providers/media/photopicker/data/model/ItemTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/model/ItemTest.java
@@ -34,29 +34,31 @@
     @Test
     public void testConstructor() {
         final long id = 1;
-        final long dataTaken = 12345678l;
+        final long dateTaken = 12345678l;
         final String mimeType = "image/png";
         final String displayName = "123.png";
         final String volumeName = "primary";
+        final int duration = 1000;
 
         final Cursor cursor = generateCursorForItem(id, mimeType, displayName, volumeName,
-                dataTaken);
+                dateTaken, duration);
         cursor.moveToFirst();
 
         final Item item = new Item(cursor);
 
         assertThat(item.getId()).isEqualTo(id);
-        assertThat(item.getDataTaken()).isEqualTo(dataTaken);
+        assertThat(item.getDateTaken()).isEqualTo(dateTaken);
         assertThat(item.getDisplayName()).isEqualTo(displayName);
         assertThat(item.getMimeType()).isEqualTo(mimeType);
         assertThat(item.getVolumeName()).isEqualTo(volumeName);
+        assertThat(item.getDuration()).isEqualTo(duration);
     }
 
     private static Cursor generateCursorForItem(long id, String mimeType,
-            String displayName, String volumeName, long dataTaken) {
+            String displayName, String volumeName, long dateTaken, int duration) {
         final MatrixCursor cursor = new MatrixCursor(
                 ItemColumns.ALL_COLUMNS_LIST.toArray(new String[0]));
-        cursor.addRow(new Object[] {id, mimeType, displayName, volumeName, dataTaken});
+        cursor.addRow(new Object[] {id, mimeType, displayName, volumeName, dateTaken, duration});
         return cursor;
     }
 }