Media views now dynamically transition between media hosts

Let there be hosts and transitions.
This also enables us to build again after the rebase

Bug: 154137987
Test: atest SystemUITests
Change-Id: I8aaed1718b35be46abae0b0255793f37b8a1fbd2
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 0297c90..673fafa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -18,32 +18,23 @@
 
 import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
 
-import android.annotation.LayoutRes;
 import android.app.PendingIntent;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.ColorStateList;
-import android.graphics.Bitmap;
-import android.graphics.ImageDecoder;
-import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 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.MediaController.PlaybackInfo;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
-import android.net.Uri;
 import android.service.media.MediaBrowserService;
-import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -56,10 +47,11 @@
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
+import androidx.constraintlayout.motion.widget.Key;
+import androidx.constraintlayout.motion.widget.KeyAttributes;
+import androidx.constraintlayout.motion.widget.KeyFrames;
 import androidx.constraintlayout.motion.widget.MotionLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
-import androidx.core.graphics.drawable.RoundedBitmapDrawable;
-import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
 
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
@@ -73,7 +65,7 @@
 
 import org.jetbrains.annotations.NotNull;
 
-import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -100,7 +92,8 @@
     private final ActivityStarter mActivityStarter;
 
     private Context mContext;
-    protected MotionLayout mMediaNotifView;
+    private MotionLayout mMediaNotifView;
+    private final View mBackground;
     private View mSeamless;
     private MediaSession.Token mToken;
     private MediaController mController;
@@ -109,6 +102,7 @@
     private MediaDevice mDevice;
     protected ComponentName mServiceComponent;
     private boolean mIsRegistered = false;
+    private final List<KeyFrames> mKeyFrames;
     private String mKey;
 
     public static final String MEDIA_PREFERENCES = "media_control_prefs";
@@ -184,8 +178,6 @@
      * @param context
      * @param parent
      * @param routeManager Manager used to listen for device change events.
-     * @param layoutId layout resource to use for this control panel
-     * @param actionIds resource IDs for action buttons in the layout
      * @param foregroundExecutor foreground executor
      * @param backgroundExecutor background executor, used for processing artwork
      * @param activityStarter activity starter
@@ -196,6 +188,8 @@
         mContext = context;
         LayoutInflater inflater = LayoutInflater.from(mContext);
         mMediaNotifView = (MotionLayout) inflater.inflate(R.layout.qs_media_panel, parent, false);
+        mBackground = mMediaNotifView.findViewById(R.id.media_background);
+        mKeyFrames = mMediaNotifView.getDefinedTransitions().get(0).getKeyFrameList();
         // TODO(b/150854549): removeOnAttachStateChangeListener when this doesn't inflate views
         // mStateListener shouldn't need to be unregistered since this object shares the same
         // lifecycle with the inflated view. It would be better, however, if this controller used an
@@ -250,7 +244,7 @@
      * Bind this view based on the data given
      */
     public void bind(@NotNull MediaData data) {
-        mToken = data.getToken();
+        MediaSession.Token token = data.getToken();
         mForegroundColor = data.getForegroundColor();
         mBackgroundColor = data.getBackgroundColor();
         if (mToken == null || !mToken.equals(token)) {
@@ -265,7 +259,6 @@
         }
 
         mController = new MediaController(mContext, mToken);
-        mKey = key;
 
         // Try to find a browser service component for this app
         // TODO also check for a media button receiver intended for restarting (b/154127084)
@@ -291,9 +284,8 @@
 
         mController.registerCallback(mSessionCallback);
 
-        albumView.setImageDrawable(data.getArtwork());
-
-        mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
+        mMediaNotifView.requireViewById(R.id.media_background).setBackgroundTintList(
+                ColorStateList.valueOf(mBackgroundColor));
 
         // Click action
         PendingIntent clickIntent = data.getClickIntent();
@@ -303,6 +295,9 @@
             });
         }
 
+        ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
+        albumView.setImageDrawable(data.getArtwork());
+
         // App icon
         ImageView appIcon = mMediaNotifView.requireViewById(R.id.icon);
         // TODO: look at iconDrawable
@@ -310,14 +305,21 @@
         iconDrawable.setTint(mForegroundColor);
         appIcon.setImageDrawable(iconDrawable);
 
+        // Song name
         TextView titleText = mMediaNotifView.requireViewById(R.id.header_title);
         titleText.setText(data.getSong());
+        titleText.setTextColor(data.getForegroundColor());
+
+        // App title
         TextView appName = mMediaNotifView.requireViewById(R.id.app_name);
         appName.setText(data.getApp());
         appName.setTextColor(mForegroundColor);
+
+        // Artist name
         TextView artistText = mMediaNotifView.requireViewById(R.id.header_artist);
         artistText.setText(data.getArtist());
         artistText.setTextColor(mForegroundColor);
+
         // Transfer chip
         mSeamless = mMediaNotifView.findViewById(R.id.media_seamless);
         if (mSeamless != null) {
@@ -352,15 +354,16 @@
         int i = 0;
         List<MediaAction> actionIcons = data.getActions();
         for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) {
-            final ImageButton button = mMediaNotifView.findViewById(ACTION_IDS[i]);
+            int actionId = ACTION_IDS[i];
+            final ImageButton button = mMediaNotifView.findViewById(actionId);
             MediaAction mediaAction = actionIcons.get(i);
             button.setImageDrawable(mediaAction.getDrawable());
             button.setContentDescription(mediaAction.getContentDescription());
             button.setImageTintList(ColorStateList.valueOf(mForegroundColor));
             PendingIntent actionIntent = mediaAction.getIntent();
 
-            if (mMediaNotifView.getBackground() instanceof IlluminationDrawable) {
-                ((IlluminationDrawable) mMediaNotifView.getBackground())
+            if (mBackground.getBackground() instanceof IlluminationDrawable) {
+                ((IlluminationDrawable) mBackground.getBackground())
                         .setupTouch(button, mMediaNotifView);
             }
 
@@ -374,9 +377,10 @@
                 }
             });
             boolean visibleInCompat = actionsWhenCollapsed.contains(i);
-            collapsedSet.setVisibility(ACTION_IDS[i],
+            updateKeyFrameVisibility(actionId, visibleInCompat);
+            collapsedSet.setVisibility(actionId,
                     visibleInCompat ? ConstraintSet.VISIBLE : ConstraintSet.GONE);
-            expandedSet.setVisibility(ACTION_IDS[i], ConstraintSet.VISIBLE);
+            expandedSet.setVisibility(actionId, ConstraintSet.VISIBLE);
         }
 
         // Hide any unused buttons
@@ -394,41 +398,26 @@
         // TODO: b/156036025 bring back media guts
 
         makeActive();
+    }
 
-        // App title (not in mini player)
-        TextView appName = mMediaNotifView.findViewById(R.id.app_name);
-        if (appName != null) {
-            appName.setText(appNameString);
-            appName.setTextColor(mForegroundColor);
-        }
-
-        // Can be null!
-        MediaMetadata mediaMetadata = mController.getMetadata();
-
-        ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
-        if (albumView != null) {
-            // Resize art in a background thread
-            mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, largeIcon, albumView));
-        }
-
-        // Song name
-        TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
-        String songName = "";
-        if (mediaMetadata != null) {
-            songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
-        }
-        titleText.setText(songName);
-        titleText.setTextColor(mForegroundColor);
-
-        // Artist name (not in mini player)
-        TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
-        if (artistText != null) {
-            String artistName = "";
-            if (mediaMetadata != null) {
-                artistName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+    /**
+     * Updates the keyframe visibility such that only views that are not visible actually go
+     * through a transition and fade in.
+     *
+     * @param actionId the id to change
+     * @param visible is the view visible
+     */
+    private void updateKeyFrameVisibility(int actionId, boolean visible) {
+        for (int i = 0; i < mKeyFrames.size(); i++) {
+            KeyFrames keyframe = mKeyFrames.get(i);
+            ArrayList<Key> viewKeyFrames = keyframe.getKeyFramesForView(actionId);
+            for (int j = 0; j < viewKeyFrames.size(); j++) {
+                Key key = viewKeyFrames.get(j);
+                if (key instanceof KeyAttributes) {
+                    KeyAttributes attributes = (KeyAttributes) key;
+                    attributes.setValue("alpha", visible ? 1.0f : 0.0f);
+                }
             }
-            artistText.setText(artistName);
-            artistText.setTextColor(mForegroundColor);
         }
     }
 
@@ -502,120 +491,6 @@
     }
 
     /**
-     * Process album art for layout
-     * @param description media description
-     * @param albumView view to hold the album art
-     */
-    protected void processAlbumArt(MediaDescription description, ImageView albumView) {
-        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, Icon largeIcon, ImageView albumView) {
-        Bitmap albumArt = null;
-
-        if (metadata != null) {
-            // First look in URI fields
-            for (String field : ART_URIS) {
-                String uriString = metadata.getString(field);
-                if (!TextUtils.isEmpty(uriString)) {
-                    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);
-    }
-
-    /**
-     * Load a bitmap from a URI
-     * @param uri
-     * @return bitmap, or null if couldn't be loaded
-     */
-    private Bitmap loadBitmapFromUri(Uri uri) {
-        // ImageDecoder requires a scheme of the following types
-        if (uri.getScheme() == null) {
-            return null;
-        }
-
-        if (!uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)
-                && !uri.getScheme().equals(ContentResolver.SCHEME_ANDROID_RESOURCE)
-                && !uri.getScheme().equals(ContentResolver.SCHEME_FILE)) {
-            return null;
-        }
-
-        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 = ThumbnailUtils.extractThumbnail(original, albumSize, albumSize);
-            roundedDrawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled);
-            roundedDrawable.setCornerRadius(radius);
-        } else {
-            Log.e(TAG, "No album art available");
-        }
-
-        // Now that it's resized, update the UI
-        final RoundedBitmapDrawable result = roundedDrawable;
-        mForegroundExecutor.execute(() -> {
-            if (result != null) {
-                albumView.setImageDrawable(result);
-                albumView.setVisibility(View.VISIBLE);
-            } else {
-                albumView.setImageDrawable(null);
-                albumView.setVisibility(View.GONE);
-            }
-        });
-    }
-
-    /**
      * Update the current device information
      * @param device device information to display
      */