Merge "Fix control bar animation" into pi-car-dev
diff --git a/car-apps-common/src/com/android/car/apps/common/widget/PagedRecyclerView.java b/car-apps-common/src/com/android/car/apps/common/widget/PagedRecyclerView.java
index 9248329..a8ebdba 100644
--- a/car-apps-common/src/com/android/car/apps/common/widget/PagedRecyclerView.java
+++ b/car-apps-common/src/com/android/car/apps/common/widget/PagedRecyclerView.java
@@ -23,6 +23,7 @@
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.View;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 
 import androidx.annotation.IntDef;
@@ -243,7 +244,7 @@
                 R.styleable.PagedRecyclerView_scrollBarContainerWidth, carMargin);
 
         mScrollBarPosition = a.getInt(R.styleable.PagedRecyclerView_scrollBarPosition,
-                    ScrollBarPosition.START);
+                ScrollBarPosition.START);
 
         mScrollBarAboveRecyclerView = a.getBoolean(
                 R.styleable.PagedRecyclerView_scrollBarAboveRecyclerView, /* defValue= */true);
@@ -414,6 +415,25 @@
         }
     }
 
+    @Override
+    public ViewHolder findContainingViewHolder(View view) {
+        if (mScrollBarEnabled) {
+            return mNestedRecyclerView.findContainingViewHolder(view);
+        } else {
+            return super.findContainingViewHolder(view);
+        }
+    }
+
+    @Override
+    @Nullable
+    public View findChildViewUnder(float x, float y) {
+        if (mScrollBarEnabled) {
+            return mNestedRecyclerView.findChildViewUnder(x, y);
+        } else {
+            return super.findChildViewUnder(x, y);
+        }
+    }
+
     /**
      * Calls {@link #layout(int, int, int, int)} for both this RecyclerView and the nested one.
      */
diff --git a/car-media-common/res/layout/fragment_app_selection.xml b/car-media-common/res/layout/fragment_app_selection.xml
index dc5087c..05f1dcd 100644
--- a/car-media-common/res/layout/fragment_app_selection.xml
+++ b/car-media-common/res/layout/fragment_app_selection.xml
@@ -49,7 +49,7 @@
         android:background="@drawable/appbar_view_icon_background"
         android:gravity="center" />
 
-    <androidx.recyclerview.widget.RecyclerView
+    <com.android.car.apps.common.widget.PagedRecyclerView
         android:id="@+id/apps_grid"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/car-media-common/res/values/bools.xml b/car-media-common/res/values/bools.xml
index c51dcc5..f1f45fe 100644
--- a/car-media-common/res/values/bools.xml
+++ b/car-media-common/res/values/bools.xml
@@ -16,4 +16,12 @@
 -->
 <resources>
     <bool name="use_media_source_color_for_fab_spinner">true</bool>
+
+    <!-- This value must remain false for production builds. It is intended to be overlaid in
+        the simulator, so media app developers can notice quickly that they are sending invalid
+        art (see MediaItemMetadata and MediaButtonController).
+        The same effect can be achieved with: adb shell setprop log.tag.MediaItemFlagInvalidArt 1
+    -->
+    <bool name="flag_invalid_media_art">false</bool>
+
 </resources>
diff --git a/car-media-common/src/com/android/car/media/common/MediaButtonController.java b/car-media-common/src/com/android/car/media/common/MediaButtonController.java
index 71a1d64..c4a2e8d 100644
--- a/car-media-common/src/com/android/car/media/common/MediaButtonController.java
+++ b/car-media-common/src/com/android/car/media/common/MediaButtonController.java
@@ -20,6 +20,7 @@
 import android.content.res.ColorStateList;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.VectorDrawable;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -43,7 +44,10 @@
 import java.util.stream.Collectors;
 
 /**
- * This class manages the media control buttons that are added to a CarControlBar
+ * This class manages the media control buttons that are added to a CarControlBar.
+ *
+ * It expects the icons to be {@link VectorDrawable}s (because they look better), and will flag
+ * non compliant ones if {@link MediaItemMetadata#flagInvalidMediaArt} returns true.
  */
 public class MediaButtonController {
 
@@ -131,7 +135,13 @@
 
     private ImageButton createIconButton(Drawable icon) {
         ImageButton button = mControlBar.createIconButton(icon);
-        button.setImageTintList(mIconsColor);
+        boolean flagInvalidArt = MediaItemMetadata.flagInvalidMediaArt(mContext);
+        if (flagInvalidArt && !(icon instanceof VectorDrawable)) {
+            button.setImageTintList(
+                    ColorStateList.valueOf(MediaItemMetadata.INVALID_MEDIA_ART_TINT_COLOR));
+        } else {
+            button.setImageTintList(mIconsColor);
+        }
         button.setImageTintMode(PorterDuff.Mode.SRC_ATOP);
         return button;
     }
diff --git a/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java b/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java
index 2fd5d15..d1a3a92 100644
--- a/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java
+++ b/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java
@@ -16,10 +16,13 @@
 
 package com.android.car.media.common;
 
+import static android.graphics.Bitmap.Config.ARGB_8888;
+
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -55,20 +58,21 @@
 
 /**
  * Abstract representation of a media item metadata.
+ *
+ * For media art, only local uris are supported so downloads can be attributed to the media app.
+ * Bitmaps are not supported because they slow down the binder.
  */
 public class MediaItemMetadata implements Parcelable {
     private static final String TAG = "MediaItemMetadata";
 
-    // STOPSHIP(arnaudberry) restore remote uri blocking.
-    // To block remote uris: adb shell setprop log.tag.MediaItemRemoteOK 0
-    private static final String ALLOW_REMOTE_URIS_KEY = "log.tag.MediaItemRemoteOK";
+    /**
+     * To red tint invalid art: adb root && adb shell setprop com.android.car.media.FlagInvalidArt 1
+     * Or set R.bool.flag_invalid_media_art to true.
+     */
+    static final String FLAG_INVALID_MEDIA_ART_KEY = "com.android.car.media.FlagInvalidArt";
+    static final int INVALID_MEDIA_ART_TINT_COLOR = Color.argb(200, 255, 0, 0);
 
-    // To red tint bitmaps:  adb shell setprop log.tag.MediaItemFlagBitmaps 1
-    private static final String FLAG_BITMAPS_KEY = "log.tag.MediaItemFlagBitmaps";
-
-    /** Can be used to tint the bitmaps in red so apps switch to content uris. */
-    // STOPSHIP(arnaudberry) decide whether to keep this or not.
-    private static final int BITMAP_WARNING_COLOR = Color.argb(100, 255, 0, 0);
+    private static Boolean sFlagNonLocalMediaArt;
 
     @NonNull
     private final MediaDescriptionCompat mMediaDescription;
@@ -209,12 +213,13 @@
                 == MediaDescriptionCompat.STATUS_DOWNLOADED;
     }
 
-    private static boolean allowRemoteUris() {
-        return "1".equals(SystemProperties.get(ALLOW_REMOTE_URIS_KEY, "1"));
-    }
-
-    private static boolean flagBitmaps() {
-        return "1".equals(SystemProperties.get(FLAG_BITMAPS_KEY, "0"));
+    static boolean flagInvalidMediaArt(Context context) {
+        if (sFlagNonLocalMediaArt == null) {
+            Resources res = context.getResources();
+            sFlagNonLocalMediaArt = res.getBoolean(R.bool.flag_invalid_media_art)
+                    || "1".equals(SystemProperties.get(FLAG_INVALID_MEDIA_ART_KEY, "0"));
+        }
+        return sFlagNonLocalMediaArt;
     }
 
     static Drawable getPlaceholderDrawable(Context context,
@@ -258,14 +263,16 @@
             imageView.setVisibility(View.GONE);
             return;
         }
-        Bitmap image = metadata.getAlbumArtBitmap();
-        if (image != null) {
-            imageView.setImageBitmap(image);
-            imageView.setVisibility(View.VISIBLE);
-            if (flagBitmaps() && BITMAP_WARNING_COLOR != 0) {
-                imageView.setColorFilter(BITMAP_WARNING_COLOR);
+        boolean flagInvalidArt = flagInvalidMediaArt(context);
+        // Don't even try to get the album art bitmap unless flagging is active.
+        if (flagInvalidArt) {
+            Bitmap image = metadata.getAlbumArtBitmap();
+            if (image != null) {
+                imageView.setImageBitmap(image);
+                imageView.setVisibility(View.VISIBLE);
+                imageView.setColorFilter(INVALID_MEDIA_ART_TINT_COLOR);
+                return;
             }
-            return;
         }
         Uri imageUri = metadata.getAlbumArtUri();
         if (imageUri != null) {
@@ -277,15 +284,18 @@
                 } else {
                     Log.e(TAG, "Unable to load resource " + imageUri);
                 }
-            } else if (allowRemoteUris() || UriUtils.isContentUri(imageUri)) {
+            } else if (flagInvalidArt || UriUtils.isContentUri(imageUri)) {
                 Glide.with(context)
                         .load(imageUri)
                         .apply(RequestOptions.placeholderOf(loadingIndicator))
                         .into(imageView);
-            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
-                // Most likely a web uri which is potentially unsafe to process in a system app.
-                Log.d(TAG, "unsupported uri: " + imageUri);
+                if (!UriUtils.isContentUri(imageUri)) {
+                    imageView.setColorFilter(INVALID_MEDIA_ART_TINT_COLOR);
+                }
+            } else {
+                Log.e(TAG, "unsupported uri: " + imageUri);
             }
+
             imageView.setVisibility(View.VISIBLE);
             return;
         }
@@ -315,17 +325,12 @@
      */
     public CompletableFuture<Bitmap> getAlbumArt(Context context, int width, int height,
             boolean fit) {
-        Bitmap image = getAlbumArtBitmap();
-        if (image != null) {
-            if (flagBitmaps() && BITMAP_WARNING_COLOR != 0) {
-                Bitmap clone = Bitmap.createBitmap(image.getWidth(), image.getHeight(),
-                        Bitmap.Config.ARGB_8888);
-                Canvas canvas = new Canvas(clone);
-                canvas.drawBitmap(image, 0f, 0f, new Paint());
-                canvas.drawColor(BITMAP_WARNING_COLOR);
-                return CompletableFuture.completedFuture(clone);
-            } else {
-                return CompletableFuture.completedFuture(image);
+        boolean flagInvalidArt = flagInvalidMediaArt(context);
+        // Don't even try to get the album art bitmap unless flagging is active.
+        if (flagInvalidArt) {
+            Bitmap image = getAlbumArtBitmap();
+            if (image != null) {
+                return CompletableFuture.completedFuture(flagBitmap(image));
             }
         }
         Uri imageUri = getAlbumArtUri();
@@ -334,8 +339,7 @@
                 // Glide doesn't support loading resources for other applications...
                 Drawable pic = UriUtils.getDrawable(context, UriUtils.getIconResource(imageUri));
                 if (pic != null) {
-                    Bitmap bitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(),
-                            Bitmap.Config.ARGB_8888);
+                    Bitmap bitmap = Bitmap.createBitmap(width, height, ARGB_8888);
                     Canvas canvas = new Canvas(bitmap);
                     pic.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
                     pic.draw(canvas);
@@ -345,7 +349,7 @@
                     Log.e(TAG, errorMessage);
                     return CompletableFuture.failedFuture(new Exception(errorMessage));
                 }
-            } else if (allowRemoteUris() || UriUtils.isContentUri(imageUri)) {
+            } else if (flagInvalidArt || UriUtils.isContentUri(imageUri)) {
                 CompletableFuture<Bitmap> bitmapCompletableFuture = new CompletableFuture<>();
                 RequestBuilder<Bitmap> builder = Glide.with(context)
                         .asBitmap()
@@ -359,6 +363,9 @@
                     @Override
                     public void onResourceReady(@NonNull Bitmap bitmap,
                             @Nullable Transition<? super Bitmap> transition) {
+                        if (!UriUtils.isContentUri(imageUri)) {
+                            bitmap = flagBitmap(bitmap);
+                        }
                         bitmapCompletableFuture.complete(bitmap);
                     }
 
@@ -371,17 +378,22 @@
                 builder.into(target);
                 return bitmapCompletableFuture;
             } else {
-                // Most likely a web uri which is potentially unsafe to process in a system app.
                 String errorMessage = "unsupported uri: \n" + imageUri;
-                if (Log.isLoggable(TAG, Log.DEBUG)) { // Use debug level to avoid log spam.
-                    Log.d(TAG, errorMessage);
-                }
+                Log.e(TAG, errorMessage);
                 return CompletableFuture.failedFuture(new Exception(errorMessage));
             }
         }
         return CompletableFuture.completedFuture(null);
     }
 
+    private static Bitmap flagBitmap(Bitmap image) {
+        Bitmap clone = Bitmap.createBitmap(image.getWidth(), image.getHeight(), ARGB_8888);
+        Canvas canvas = new Canvas(clone);
+        canvas.drawBitmap(image, 0f, 0f, new Paint());
+        canvas.drawColor(INVALID_MEDIA_ART_TINT_COLOR);
+        return clone;
+    }
+
     public boolean isBrowsable() {
         return mIsBrowsable;
     }