Improve album art handling

- Use ThumbnailUtils to handle resizing and cropping images to the right
dimensions

- Check alternate fields for album art information, preferring URIs over
bitmap, with largeIcon as a fallback if none of those fields are set

Fixes: 153178112
Fixes: 151054111
Fixes: 152067055
Test: visual
Change-Id: Iea23e1159ffaecac31d0f475c129e8b927a38b86
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index f25de6a..233d24b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -26,14 +26,18 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
+import android.graphics.ImageDecoder;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.Icon;
 import android.graphics.drawable.RippleDrawable;
 import android.media.MediaDescription;
 import android.media.MediaMetadata;
+import android.media.ThumbnailUtils;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
+import android.net.Uri;
 import android.service.media.MediaBrowserService;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -59,6 +63,7 @@
 import com.android.systemui.qs.QSMediaBrowser;
 import com.android.systemui.util.Assert;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -99,6 +104,13 @@
             com.android.internal.R.id.action4
     };
 
+    // URI fields to try loading album art from
+    private static final String[] ART_URIS = {
+            MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+            MediaMetadata.METADATA_KEY_ART_URI,
+            MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+    };
+
     private final MediaController.Callback mSessionCallback = new MediaController.Callback() {
         @Override
         public void onSessionDestroyed() {
@@ -205,14 +217,16 @@
      * Update the media panel view for the given media session
      * @param token
      * @param iconDrawable
+     * @param largeIcon
      * @param iconColor
      * @param bgColor
      * @param contentIntent
      * @param appNameString
      * @param key
      */
-    public void setMediaSession(MediaSession.Token token, Drawable iconDrawable, int iconColor,
-            int bgColor, PendingIntent contentIntent, String appNameString, String key) {
+    public void setMediaSession(MediaSession.Token token, Drawable iconDrawable, Icon largeIcon,
+            int iconColor, int bgColor, PendingIntent contentIntent, String appNameString,
+            String key) {
         // Ensure that component names are updated if token has changed
         if (mToken == null || !mToken.equals(token)) {
             mToken = token;
@@ -303,7 +317,7 @@
         ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
         if (albumView != null) {
             // Resize art in a background thread
-            mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, albumView));
+            mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, largeIcon, albumView));
         }
 
         // Song name
@@ -396,30 +410,82 @@
      * @param albumView view to hold the album art
      */
     protected void processAlbumArt(MediaDescription description, ImageView albumView) {
-        Bitmap albumArt = description.getIconBitmap();
-        //TODO check other fields (b/151054111, b/152067055)
+        Bitmap albumArt = null;
+
+        // First try loading from URI
+        albumArt = loadBitmapFromUri(description.getIconUri());
+
+        // Then check bitmap
+        if (albumArt == null) {
+            albumArt = description.getIconBitmap();
+        }
+
         processAlbumArtInternal(albumArt, albumView);
     }
 
     /**
      * Process album art for layout
      * @param metadata media metadata
+     * @param largeIcon from notification, checked as a fallback if metadata does not have art
      * @param albumView view to hold the album art
      */
-    private void processAlbumArt(MediaMetadata metadata, ImageView albumView) {
-        Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
-        //TODO check other fields (b/151054111, b/152067055)
+    private void processAlbumArt(MediaMetadata metadata, Icon largeIcon, ImageView albumView) {
+        Bitmap albumArt = null;
+
+        // First look in URI fields
+        for (String field : ART_URIS) {
+            String uriString = metadata.getString(field);
+            if (uriString != null) {
+                albumArt = loadBitmapFromUri(Uri.parse(uriString));
+                if (albumArt != null) {
+                    Log.d(TAG, "loaded art from " + field);
+                    break;
+                }
+            }
+        }
+
+        // Then check bitmap field
+        if (albumArt == null) {
+            albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        }
+
+        // Finally try the notification's largeIcon
+        if (albumArt == null && largeIcon != null) {
+            albumArt = largeIcon.getBitmap();
+        }
+
         processAlbumArtInternal(albumArt, albumView);
     }
 
-    private void processAlbumArtInternal(Bitmap albumArt, ImageView albumView) {
-        float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
+    /**
+     * Load a bitmap from a URI
+     * @param uri
+     * @return bitmap, or null if couldn't be loaded
+     */
+    private Bitmap loadBitmapFromUri(Uri uri) {
+        ImageDecoder.Source source = ImageDecoder.createSource(mContext.getContentResolver(), uri);
+        try {
+            return ImageDecoder.decodeBitmap(source);
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Resize and crop the image if provided and update the control view
+     * @param albumArt Bitmap of art to display, or null to hide view
+     * @param albumView View that will hold the art
+     */
+    private void processAlbumArtInternal(@Nullable Bitmap albumArt, ImageView albumView) {
+        // Resize
         RoundedBitmapDrawable roundedDrawable = null;
         if (albumArt != null) {
+            float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
             Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
             int albumSize = (int) mContext.getResources().getDimension(
                     R.dimen.qs_media_album_size);
-            Bitmap scaled = Bitmap.createScaledBitmap(original, albumSize, albumSize, false);
+            Bitmap scaled = ThumbnailUtils.extractThumbnail(original, albumSize, albumSize);
             roundedDrawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled);
             roundedDrawable.setCornerRadius(radius);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
index 0f06566..9e574a1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -23,6 +23,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.media.MediaDescription;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
@@ -115,8 +116,8 @@
         }
 
         // Set what we can normally
-        super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, appName.toString(),
-                null);
+        super.setMediaSession(token, icon, null, iconColor, bgColor, contentIntent,
+                appName.toString(), null);
 
         // Then add info from MediaDescription
         ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
@@ -149,6 +150,7 @@
      * Update media panel view for the given media session
      * @param token token for this media session
      * @param icon app notification icon
+     * @param largeIcon notification's largeIcon, used as a fallback for album art
      * @param iconColor foreground color (for text, icons)
      * @param bgColor background color
      * @param actionsContainer a LinearLayout containing the media action buttons
@@ -156,11 +158,12 @@
      * @param appName Application title
      * @param key original notification's key
      */
-    public void setMediaSession(MediaSession.Token token, Drawable icon, int iconColor,
-            int bgColor, View actionsContainer, PendingIntent contentIntent, String appName,
-            String key) {
+    public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+            int iconColor, int bgColor, View actionsContainer, PendingIntent contentIntent,
+            String appName, String key) {
 
-        super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, appName, key);
+        super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, appName,
+                key);
 
         // Media controls
         if (actionsContainer != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 1eb5778..1252008 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -32,6 +32,7 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.media.MediaDescription;
 import android.media.session.MediaSession;
 import android.metrics.LogMaker;
@@ -225,14 +226,16 @@
      * Add or update a player for the associated media session
      * @param token
      * @param icon
+     * @param largeIcon
      * @param iconColor
      * @param bgColor
      * @param actionsContainer
      * @param notif
      * @param key
      */
-    public void addMediaSession(MediaSession.Token token, Drawable icon, int iconColor, int bgColor,
-            View actionsContainer, StatusBarNotification notif, String key) {
+    public void addMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+            int iconColor, int bgColor, View actionsContainer, StatusBarNotification notif,
+            String key) {
         if (!useQsMediaPlayer(mContext)) {
             // Shouldn't happen, but just in case
             Log.e(TAG, "Tried to add media session without player!");
@@ -296,7 +299,7 @@
         Log.d(TAG, "setting player session");
         String appName = Notification.Builder.recoverBuilder(getContext(), notif.getNotification())
                 .loadHeaderAppName();
-        player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer,
+        player.setMediaSession(token, icon, largeIcon, iconColor, bgColor, actionsContainer,
                 notif.getNotification().contentIntent, appName, key);
 
         if (mMediaPlayers.size() > 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
index 7ba7c5f..89b36da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -19,6 +19,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.view.View;
@@ -58,6 +59,7 @@
      * Update media panel view for the given media session
      * @param token token for this media session
      * @param icon app notification icon
+     * @param largeIcon notification's largeIcon, used as a fallback for album art
      * @param iconColor foreground color (for text, icons)
      * @param bgColor background color
      * @param actionsContainer a LinearLayout containing the media action buttons
@@ -66,8 +68,9 @@
      * @param contentIntent Intent to send when user taps on the view
      * @param key original notification's key
      */
-    public void setMediaSession(MediaSession.Token token, Drawable icon, int iconColor, int bgColor,
-            View actionsContainer, int[] actionsToShow, PendingIntent contentIntent, String key) {
+    public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+            int iconColor, int bgColor, View actionsContainer, int[] actionsToShow,
+            PendingIntent contentIntent, String key) {
         // Only update if this is a different session and currently playing
         String oldPackage = "";
         if (getController() != null) {
@@ -82,7 +85,7 @@
             return;
         }
 
-        super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, null, key);
+        super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, null, key);
 
         LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
         int i = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 796f22c..b96cff8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -191,6 +191,7 @@
             Drawable iconDrawable = notif.getSmallIcon().loadDrawable(mContext);
             panel.getMediaPlayer().setMediaSession(token,
                     iconDrawable,
+                    notif.getLargeIcon(),
                     tintColor,
                     mBackgroundColor,
                     mActions,
@@ -201,6 +202,7 @@
                     com.android.systemui.R.id.quick_settings_panel);
             bigPanel.addMediaSession(token,
                     iconDrawable,
+                    notif.getLargeIcon(),
                     tintColor,
                     mBackgroundColor,
                     mActions,