[automerger skipped] Apply androidx.media2.* library/package separation to pump app am: 862df21538 am: 4e72d86717
am: ff85df5beb -s ours
am skip reason: change_id Id6dbfedb0ca3b39e992c53ad5a0903c8c082a8bf with SHA1 ec423bc6b3 is in history

Change-Id: I4498bb79617ea0b38659f0fc4358497021317c5b
diff --git a/Android.bp b/Android.bp
index 3c7f1e2..b12b53c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -25,6 +25,7 @@
     static_libs: [
         "androidx-constraintlayout_constraintlayout",
         "androidx.media2_media2-widget",
+        "androidx.media2_media2-player",
         "com.google.android.material_material"
     ],
     optimize: {
diff --git a/build.gradle b/build.gradle
index cf219d4..2f87314 100644
--- a/build.gradle
+++ b/build.gradle
@@ -63,6 +63,7 @@
 
 dependencies {
     implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
-    implementation 'androidx.media2:media2-widget:1.0.0-alpha07'
+    implementation 'androidx.media2:media2-widget:+'
+    implementation 'androidx.media2:media2-player:+'
     implementation 'com.google.android.material:material:1.0.0'
 }
diff --git a/java/com/android/pump/activity/AudioPlayerActivity.java b/java/com/android/pump/activity/AudioPlayerActivity.java
index d55d286..cfdb651 100644
--- a/java/com/android/pump/activity/AudioPlayerActivity.java
+++ b/java/com/android/pump/activity/AudioPlayerActivity.java
@@ -27,7 +27,9 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.appcompat.app.AppCompatActivity;
+import androidx.media.AudioAttributesCompat;
 import androidx.media2.common.UriMediaItem;
+import androidx.media2.player.MediaPlayer;
 import androidx.media2.widget.VideoView;
 
 import com.android.pump.R;
@@ -44,6 +46,7 @@
     private static final String TAG = Clog.tag(AudioPlayerActivity.class);
 
     private VideoView mVideoView;
+    private MediaPlayer mMediaPlayer;
 
     public static void start(@NonNull Context context, @NonNull Audio audio) {
         // TODO(b/123702587) Find a better URI (audio.getUri()?)
@@ -118,6 +121,14 @@
         setContentView(R.layout.activity_audio_player);
         mVideoView = findViewById(R.id.video_view);
 
+        mMediaPlayer = new MediaPlayer(AudioPlayerActivity.this);
+        AudioAttributesCompat audioAttributes = new AudioAttributesCompat.Builder()
+                .setUsage(AudioAttributesCompat.USAGE_MEDIA)
+                .setContentType(AudioAttributesCompat.CONTENT_TYPE_MOVIE).build();
+
+        mMediaPlayer.setAudioAttributes(audioAttributes);
+        mVideoView.setPlayer(mMediaPlayer);
+
         handleIntent();
     }
 
@@ -138,6 +149,16 @@
             return;
         }
         UriMediaItem mediaItem = new UriMediaItem.Builder(uri).build();
-        mVideoView.setMediaItem(mediaItem);
+        mMediaPlayer.setMediaItem(mediaItem);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        try {
+            if (mMediaPlayer != null) {
+                mMediaPlayer.close();
+            }
+        } catch (Exception e) { }
     }
 }
diff --git a/java/com/android/pump/activity/VideoPlayerActivity.java b/java/com/android/pump/activity/VideoPlayerActivity.java
index 1e75c40..1ef7709 100644
--- a/java/com/android/pump/activity/VideoPlayerActivity.java
+++ b/java/com/android/pump/activity/VideoPlayerActivity.java
@@ -27,15 +27,14 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.appcompat.app.AppCompatActivity;
-import androidx.media2.common.MediaItem;
+import androidx.core.content.ContextCompat;
+import androidx.media.AudioAttributesCompat;
 import androidx.media2.common.SessionPlayer;
 import androidx.media2.common.UriMediaItem;
-import androidx.media2.session.MediaController;
-import androidx.media2.session.SessionToken;
+import androidx.media2.player.MediaPlayer;
 import androidx.media2.widget.VideoView;
 
 import com.android.pump.R;
-import com.android.pump.concurrent.Executors;
 import com.android.pump.db.Video;
 import com.android.pump.util.Clog;
 import com.android.pump.util.IntentUtils;
@@ -46,7 +45,7 @@
     private static final String SAVED_POSITION_KEY = "SavedPosition";
 
     private VideoView mVideoView;
-    private MediaController mMediaController;
+    private MediaPlayer mMediaPlayer;
     private long mSavedPosition = SessionPlayer.UNKNOWN_TIME;
 
     public static void start(@NonNull Context context, @NonNull Video video) {
@@ -70,38 +69,24 @@
                     SessionPlayer.UNKNOWN_TIME);
         }
 
+        mMediaPlayer = new MediaPlayer(VideoPlayerActivity.this);
+        AudioAttributesCompat audioAttributes = new AudioAttributesCompat.Builder()
+                .setUsage(AudioAttributesCompat.USAGE_MEDIA)
+                .setContentType(AudioAttributesCompat.CONTENT_TYPE_MOVIE).build();
+
+        mMediaPlayer.setAudioAttributes(audioAttributes);
+        mVideoView.setPlayer(mMediaPlayer);
+
         handleIntent();
     }
 
     @Override
     public void onSaveInstanceState(@NonNull Bundle outState) {
-        if (mMediaController != null) {
-            outState.putLong(SAVED_POSITION_KEY, mMediaController.getCurrentPosition());
-        }
-
+        outState.putLong(SAVED_POSITION_KEY, mMediaPlayer.getCurrentPosition());
         super.onSaveInstanceState(outState);
     }
 
     @Override
-    public void onAttachedToWindow() {
-        if (mMediaController == null) {
-            SessionToken token = mVideoView.getSessionToken();
-            mMediaController = new MediaController.Builder(this)
-                    .setSessionToken(token)
-                    .setControllerCallback(Executors.uiThreadExecutor(), new ControllerCallback())
-                    .build();
-        }
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        if (mMediaController != null) {
-            mMediaController.close();
-            mMediaController = null;
-        }
-    }
-
-    @Override
     protected void onNewIntent(@Nullable Intent intent) {
         super.onNewIntent(intent);
         setIntent(intent);
@@ -118,18 +103,26 @@
             return;
         }
         UriMediaItem mediaItem = new UriMediaItem.Builder(uri).build();
-        mVideoView.setMediaItem(mediaItem);
+        mMediaPlayer.setMediaItem(mediaItem)
+                .addListener(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mSavedPosition != SessionPlayer.UNKNOWN_TIME) {
+                            mMediaPlayer.seekTo(mSavedPosition);
+                            mSavedPosition = SessionPlayer.UNKNOWN_TIME;
+                        }
+                        mMediaPlayer.play();
+                    }
+                }, ContextCompat.getMainExecutor(this));
     }
 
-    private class ControllerCallback extends MediaController.ControllerCallback {
-        @Override
-        public void onCurrentMediaItemChanged(@NonNull MediaController controller,
-                @Nullable MediaItem item) {
-            if (mSavedPosition != SessionPlayer.UNKNOWN_TIME) {
-                controller.seekTo(mSavedPosition);
-                mSavedPosition = SessionPlayer.UNKNOWN_TIME;
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        try {
+            if (mMediaPlayer != null) {
+                mMediaPlayer.close();
             }
-            controller.play();
-        }
+        } catch (Exception e) { }
     }
 }
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 {