Merge Android R (rvc-dev-plus-aosp-without-vendor@6692709)

Bug: 166295507
Merged-In: I5a6abeeeab91e9f2ce7e6f49e863d76f21bd44d6
Change-Id: I5667593cf89e4432861a5033ea57281507ddce74
diff --git a/java/com/android/pump/app/GlobalsApplication.java b/java/com/android/pump/app/GlobalsApplication.java
index dc30372..27e1e43 100644
--- a/java/com/android/pump/app/GlobalsApplication.java
+++ b/java/com/android/pump/app/GlobalsApplication.java
@@ -32,7 +32,7 @@
     @Override
     public @NonNull ImageLoader getImageLoader() {
         if (mImageLoader == null) {
-            mImageLoader = new ImageLoader(getExecutor());
+            mImageLoader = new ImageLoader(getContentResolver(), getExecutor());
         }
         return mImageLoader;
     }
diff --git a/java/com/android/pump/db/AudioStore.java b/java/com/android/pump/db/AudioStore.java
index 4cd569b..9058a20 100644
--- a/java/com/android/pump/db/AudioStore.java
+++ b/java/com/android/pump/db/AudioStore.java
@@ -31,7 +31,6 @@
 import com.android.pump.util.Clog;
 import com.android.pump.util.Collections;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.Collection;
 
@@ -317,7 +316,6 @@
 
         Uri contentUri = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
         String[] projection = {
-            MediaStore.Audio.Albums.ALBUM_ART,
             MediaStore.Audio.Albums.ALBUM,
             MediaStore.Audio.Media.ARTIST_ID // TODO MediaStore.Audio.Albums.ARTIST_ID
         };
@@ -327,8 +325,6 @@
                 contentUri, projection, selection, selectionArgs, null);
         if (cursor != null) {
             try {
-                int albumArtColumn = cursor.getColumnIndexOrThrow(
-                        MediaStore.Audio.Albums.ALBUM_ART);
                 int albumColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM);
                 int artistIdColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID); // TODO MediaStore.Audio.Albums.ARTIST_ID
 
@@ -337,16 +333,20 @@
                         String albumTitle = cursor.getString(albumColumn);
                         updated |= album.setTitle(albumTitle);
                     }
-                    if (!cursor.isNull(albumArtColumn)) {
-                        Uri albumArtUri = Uri.fromFile(new File(cursor.getString(albumArtColumn)));
-                        updated |= album.setAlbumArtUri(albumArtUri);
-                    }
                     if (!cursor.isNull(artistIdColumn)) {
                         long artistId = cursor.getLong(artistIdColumn);
                         Artist artist = mMediaProvider.getArtistById(artistId);
                         updated |= album.setArtist(artist);
                         updated |= loadData(artist); // TODO(b/123707561) Load separate from album
                     }
+
+                    // TODO(b/130363861) No need to store the URI -- generate when requested instead
+                    Uri albumArtUri = new Uri.Builder()
+                            .scheme(ContentResolver.SCHEME_CONTENT)
+                            .authority(MediaStore.AUTHORITY)
+                            .appendPath("external").appendPath("audio").appendPath("albumart")
+                            .appendPath(Long.toString(album.getId())).build();
+                    updated |= album.setAlbumArtUri(albumArtUri);
                 }
             } finally {
                 cursor.close();
diff --git a/java/com/android/pump/db/VideoStore.java b/java/com/android/pump/db/VideoStore.java
index 657c0c1..4f00b2a 100644
--- a/java/com/android/pump/db/VideoStore.java
+++ b/java/com/android/pump/db/VideoStore.java
@@ -17,6 +17,7 @@
 package com.android.pump.db;
 
 import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
@@ -43,7 +44,7 @@
     private static final String RELATIVE_PATH = "relative_path";
 
     // TODO Replace with Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q throughout the code.
-    private static boolean isRunningQ() {
+    private static boolean isAtLeastRunningQ() {
         return Build.VERSION.SDK_INT > Build.VERSION_CODES.P
                 || (Build.VERSION.SDK_INT == Build.VERSION_CODES.P
                 && Build.VERSION.PREVIEW_SDK_INT > 0);
@@ -101,7 +102,7 @@
         {
             Uri contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
             String[] projection;
-            if (isRunningQ()) {
+            if (isAtLeastRunningQ()) {
                 projection = new String[] {
                     MediaStore.Video.Media._ID,
                     MediaStore.Video.Media.MIME_TYPE,
@@ -126,7 +127,7 @@
                     int mimeTypeColumn = cursor.getColumnIndexOrThrow(
                             MediaStore.Video.Media.MIME_TYPE);
 
-                    if (isRunningQ()) {
+                    if (isAtLeastRunningQ()) {
                         dataColumn = -1;
                         relativePathColumn = cursor.getColumnIndexOrThrow(RELATIVE_PATH);
                         displayNameColumn = cursor.getColumnIndexOrThrow(
@@ -142,7 +143,7 @@
                         String mimeType = cursor.getString(mimeTypeColumn);
 
                         File file;
-                        if (isRunningQ()) {
+                        if (isAtLeastRunningQ()) {
                             String relativePath = cursor.getString(relativePathColumn);
                             String displayName = cursor.getString(displayNameColumn);
                             file = new File(relativePath, displayName);
@@ -276,35 +277,9 @@
     }
 
     private @Nullable Uri getThumbnailUri(long id) {
-        int thumbKind = MediaStore.Video.Thumbnails.MINI_KIND;
-
-        // TODO(b/123707512) The following line is required to generate thumbnails -- is there a better way?
-        MediaStore.Video.Thumbnails.getThumbnail(mContentResolver, id, thumbKind, null);
-
-        Uri thumbnailUri = null;
-        Uri contentUri = MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI;
-        String[] projection = {
-            MediaStore.Video.Thumbnails.DATA
-        };
-        String selection = MediaStore.Video.Thumbnails.KIND + " = " + thumbKind + " AND " +
-                MediaStore.Video.Thumbnails.VIDEO_ID + " = ?";
-        String[] selectionArgs = { Long.toString(id) };
-        Cursor cursor = mContentResolver.query(
-                contentUri, projection, selection, selectionArgs, null);
-        if (cursor != null) {
-            try {
-                int dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Thumbnails.DATA);
-
-                if (cursor.moveToFirst()) {
-                    String data = cursor.getString(dataColumn);
-
-                    thumbnailUri = Uri.fromFile(new File(data));
-                }
-            } finally {
-                cursor.close();
-            }
-        }
-        return thumbnailUri;
+        // TODO(b/130363861) No need to store the URI -- generate when requested instead
+        return ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id)
+                .buildUpon().appendPath("thumbnail").build();
     }
 
     @Override
diff --git a/java/com/android/pump/util/ImageLoader.java b/java/com/android/pump/util/ImageLoader.java
index ba9b89a..0a60e66 100644
--- a/java/com/android/pump/util/ImageLoader.java
+++ b/java/com/android/pump/util/ImageLoader.java
@@ -16,9 +16,14 @@
 
 package com.android.pump.util;
 
+import android.content.ContentResolver;
+import android.content.UriMatcher;
+import android.content.res.AssetFileDescriptor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.net.Uri;
+import android.os.Build;
+import android.provider.MediaStore;
 
 import androidx.annotation.AnyThread;
 import androidx.annotation.NonNull;
@@ -29,6 +34,7 @@
 import com.android.pump.concurrent.Executors;
 
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.AbstractMap.SimpleEntry;
 import java.util.LinkedList;
@@ -41,8 +47,22 @@
 public class ImageLoader {
     private static final String TAG = Clog.tag(ImageLoader.class);
 
+    // TODO Replace with Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q throughout the code.
+    private static boolean isAtLeastRunningQ() {
+        return Build.VERSION.SDK_INT > Build.VERSION_CODES.P
+                || (Build.VERSION.SDK_INT == Build.VERSION_CODES.P
+                && Build.VERSION.PREVIEW_SDK_INT > 0);
+    }
+
+    private static final UriMatcher VIDEO_THUMBNAIL_URI_MATCHER =
+            new UriMatcher(UriMatcher.NO_MATCH);
+    static {
+        VIDEO_THUMBNAIL_URI_MATCHER.addURI("media", "*/video/media/#/thumbnail", 0);
+    }
+
     private final BitmapCache mBitmapCache = new BitmapCache();
     private final OrientationCache mOrientationCache = new OrientationCache();
+    private final ContentResolver mContentResolver;
     private final Executor mExecutor;
     private final Set<Map.Entry<Executor, Callback>> mCallbacks = new ArraySet<>();
     private final Map<Uri, List<Map.Entry<Executor, Callback>>> mLoadCallbacks = new ArrayMap<>();
@@ -52,7 +72,8 @@
         void onImageLoaded(@NonNull Uri uri, @Nullable Bitmap bitmap);
     }
 
-    public ImageLoader(@NonNull Executor executor) {
+    public ImageLoader(@NonNull ContentResolver contentResolver, @NonNull Executor executor) {
+        mContentResolver = contentResolver;
         mExecutor = executor;
     }
 
@@ -121,15 +142,26 @@
         @Override
         public void run() {
             try {
-                byte[] data;
-                if (Scheme.isFile(mUri)) {
-                    data = IoUtils.readFromFile(new File(mUri.getPath()));
-                } else if (Scheme.isHttp(mUri) || Scheme.isHttps(mUri)) {
-                    data = Http.get(mUri.toString());
+                Bitmap bitmap;
+                if (isAtLeastRunningQ() || !isVideoThumbnailUri(mUri)) {
+                    byte[] data;
+                    if (Scheme.isContent(mUri)) {
+                        data = readFromContent(mUri);
+                    } else if (Scheme.isFile(mUri)) {
+                        data = IoUtils.readFromFile(new File(mUri.getPath()));
+                    } else if (Scheme.isHttp(mUri) || Scheme.isHttps(mUri)) {
+                        data = Http.get(mUri.toString());
+                    } else {
+                        throw new IllegalArgumentException(
+                                "Unknown scheme '" + mUri.getScheme() + "'");
+                    }
+                    bitmap = decodeBitmapFromByteArray(data);
                 } else {
-                    throw new IllegalArgumentException("Unknown scheme '" + mUri.getScheme() + "'");
+                    // TODO This will always return a bitmap which is inconsistent with Q.
+                    bitmap = MediaStore.Video.Thumbnails.getThumbnail(mContentResolver,
+                            Long.parseLong(mUri.getPathSegments().get(3)),
+                            MediaStore.Video.Thumbnails.MINI_KIND, null);
                 }
-                Bitmap bitmap = decodeBitmapFromByteArray(data);
                 Set<Map.Entry<Executor, Callback>> callbacks;
                 List<Map.Entry<Executor, Callback>> loadCallbacks;
                 synchronized (ImageLoader.this) { // TODO(b/123708613) proper lock
@@ -164,5 +196,23 @@
             options.inSampleSize = 1; // TODO(b/123708796) add scaling
             return BitmapFactory.decodeByteArray(data, 0, data.length, options);
         }
+
+        private @NonNull byte[] readFromContent(@NonNull Uri uri) throws IOException {
+            // TODO(b/123708796) set EXTRA_SIZE in opts
+            AssetFileDescriptor assetFileDescriptor =
+                    mContentResolver.openTypedAssetFileDescriptor(uri, "image/*", null);
+            if (assetFileDescriptor == null) {
+                throw new FileNotFoundException(uri.toString());
+            }
+            try {
+                return IoUtils.readFromAssetFileDescriptor(assetFileDescriptor);
+            } finally {
+                IoUtils.close(assetFileDescriptor);
+            }
+        }
+
+        private boolean isVideoThumbnailUri(@NonNull Uri uri) {
+            return VIDEO_THUMBNAIL_URI_MATCHER.match(uri) != UriMatcher.NO_MATCH;
+        }
     }
 }
diff --git a/java/com/android/pump/util/IoUtils.java b/java/com/android/pump/util/IoUtils.java
index c39fe35..aec3440 100644
--- a/java/com/android/pump/util/IoUtils.java
+++ b/java/com/android/pump/util/IoUtils.java
@@ -16,6 +16,8 @@
 
 package com.android.pump.util;
 
+import android.content.res.AssetFileDescriptor;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
@@ -43,6 +45,16 @@
         }
     }
 
+    public static @NonNull byte[] readFromAssetFileDescriptor(
+            @NonNull AssetFileDescriptor assetFileDescriptor) throws IOException {
+        InputStream inputStream = assetFileDescriptor.createInputStream();
+        try {
+            return readFromStream(inputStream);
+        } finally {
+            close(inputStream);
+        }
+    }
+
     public static @NonNull byte[] readFromStream(@NonNull InputStream inputStream)
             throws IOException {
         ByteArrayOutputStream buffer = new ByteArrayOutputStream();
diff --git a/java/com/android/pump/util/Scheme.java b/java/com/android/pump/util/Scheme.java
index ad544e3..7c52ec4 100644
--- a/java/com/android/pump/util/Scheme.java
+++ b/java/com/android/pump/util/Scheme.java
@@ -26,10 +26,15 @@
 public final class Scheme {
     private Scheme() { }
 
+    private final static String CONTENT = ContentResolver.SCHEME_CONTENT;
     private final static String FILE = ContentResolver.SCHEME_FILE;
     private final static String HTTP = "http";
     private final static String HTTPS = "https";
 
+    public static boolean isContent(@NonNull Uri uri) {
+        return CONTENT.equals(uri.getScheme());
+    }
+
     public static boolean isFile(@NonNull Uri uri) {
         return FILE.equals(uri.getScheme());
     }
diff --git a/java/com/android/pump/widget/UriImageView.java b/java/com/android/pump/widget/UriImageView.java
index 7381a8e..f10425e 100644
--- a/java/com/android/pump/widget/UriImageView.java
+++ b/java/com/android/pump/widget/UriImageView.java
@@ -73,7 +73,8 @@
         if (uri == null) {
             return;
         }
-        if (Scheme.isFile(uri) || Scheme.isHttp(uri) || Scheme.isHttps(uri)) {
+        if (Scheme.isContent(uri) || Scheme.isFile(uri)
+                || Scheme.isHttp(uri) || Scheme.isHttps(uri)) {
             mUri = uri;
             loadImage();
         } else {