Merge stage-aosp-master to aosp-master - DO NOT MERGE

Change-Id: I8b8e830c2a2827259198035a653d864cc795f713
diff --git a/TestMediaApp/AndroidManifest.xml b/TestMediaApp/AndroidManifest.xml
index 09e850c..911145e 100644
--- a/TestMediaApp/AndroidManifest.xml
+++ b/TestMediaApp/AndroidManifest.xml
@@ -28,10 +28,11 @@
         android:supportsRtl="true"
         android:theme="@style/TestMediaAppTheme" >
 
+        <!-- This provider is read-only, only returns album art, and is not a security risk -->
         <provider
-            android:name=".TmaAssetProvider"
+            android:name=".TmaPublicProvider"
             android:exported="true"
-            android:authorities="com.android.car.media.testmediaapp.assets"/>
+            android:authorities="com.android.car.media.testmediaapp.public"/>
 
         <service
             android:name=".TmaBrowser"
diff --git a/TestMediaApp/assets/media_items/advanced.json b/TestMediaApp/assets/media_items/advanced.json
index a6befa3..45a18ef 100644
--- a/TestMediaApp/assets/media_items/advanced.json
+++ b/TestMediaApp/assets/media_items/advanced.json
@@ -39,7 +39,7 @@
     },
     {
       "FLAGS": "browsable",
-      "PLAYABLE_HINT": "GRID",
+      "BROWSABLE_HINT": "GRID_CATEGORY",
       "METADATA": {
         "MEDIA_ID": "advanced art nodes",
         "DISPLAY_TITLE": "Album Art"
diff --git a/TestMediaApp/assets/media_items/album_art/art_nodes.json b/TestMediaApp/assets/media_items/album_art/art_nodes.json
index 434ed23..692809f 100644
--- a/TestMediaApp/assets/media_items/album_art/art_nodes.json
+++ b/TestMediaApp/assets/media_items/album_art/art_nodes.json
@@ -1,6 +1,6 @@
 {
   "FLAGS": "browsable",
-  "BROWSABLE_HINT": "LIST",
+  "BROWSABLE_HINT": "GRID_CATEGORY",
 
   "METADATA": {
     "MEDIA_ID": "album_art/art_nodes",
@@ -52,6 +52,15 @@
         "DISPLAY_TITLE": "Nature 1024"
       },
       "INCLUDE":"media_items/album_art/nature/art_nature_1024.json"
+    },
+    {
+      "FLAGS": "browsable",
+      "PLAYABLE_HINT": "GRID",
+      "METADATA": {
+        "MEDIA_ID": "album_art/art_nodes nature files",
+        "DISPLAY_TITLE": "Nature files"
+      },
+      "INCLUDE":"media_items/album_art/nature/art_nature_files.json"
     }
   ]
 }
\ No newline at end of file
diff --git a/TestMediaApp/assets/media_items/album_art/nature/art_nature_1024.json b/TestMediaApp/assets/media_items/album_art/nature/art_nature_1024.json
index b1759f2..c34ed80 100644
--- a/TestMediaApp/assets/media_items/album_art/nature/art_nature_1024.json
+++ b/TestMediaApp/assets/media_items/album_art/nature/art_nature_1024.json
@@ -13,7 +13,7 @@
         "MEDIA_ID": "art_nature_1024_leaves bee",
         "DISPLAY_TITLE": "Bee",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-1024/bee.jpg"
+        "ART_URI": "assets/bitmaps/nature-1024/bee.jpg"
       }
     },
     {
@@ -22,7 +22,7 @@
         "MEDIA_ID": "art_nature_1024_leaves clouds",
         "DISPLAY_TITLE": "Clouds",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-1024/clouds.jpg"
+        "ART_URI": "assets/bitmaps/nature-1024/clouds.jpg"
       }
     },
     {
@@ -31,7 +31,7 @@
         "MEDIA_ID": "art_nature_1024_leaves flower1",
         "DISPLAY_TITLE": "Flower 1",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-1024/flower1.jpg"
+        "ART_URI": "assets/bitmaps/nature-1024/flower1.jpg"
       }
     },
     {
@@ -40,7 +40,7 @@
         "MEDIA_ID": "art_nature_1024_leaves flower2",
         "DISPLAY_TITLE": "Flower 2",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-1024/flower2.jpg"
+        "ART_URI": "assets/bitmaps/nature-1024/flower2.jpg"
       }
     },
     {
@@ -49,7 +49,7 @@
         "MEDIA_ID": "art_nature_1024_leaves flower3",
         "DISPLAY_TITLE": "Flower3 ",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-1024/flower3.jpg"
+        "ART_URI": "assets/bitmaps/nature-1024/flower3.jpg"
       }
     },
     {
@@ -58,7 +58,7 @@
         "MEDIA_ID": "art_nature_1024_leaves flowers",
         "DISPLAY_TITLE": "Flowers",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-1024/flowers.jpg"
+        "ART_URI": "assets/bitmaps/nature-1024/flowers.jpg"
       }
     },
     {
@@ -67,7 +67,7 @@
         "MEDIA_ID": "art_nature_1024_leaves leaves",
         "DISPLAY_TITLE": "Leaves",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-1024/leaves.jpg"
+        "ART_URI": "assets/bitmaps/nature-1024/leaves.jpg"
       }
     },
     {
@@ -76,7 +76,7 @@
         "MEDIA_ID": "art_nature_1024_leaves sage",
         "DISPLAY_TITLE": "Sage",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-1024/sage.jpg"
+        "ART_URI": "assets/bitmaps/nature-1024/sage.jpg"
       }
     },
     {
@@ -85,7 +85,7 @@
         "MEDIA_ID": "art_nature_1024_leaves tree",
         "DISPLAY_TITLE": "Tree",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-1024/tree.jpg"
+        "ART_URI": "assets/bitmaps/nature-1024/tree.jpg"
       }
     }
   ]
diff --git a/TestMediaApp/assets/media_items/album_art/nature/art_nature_128.json b/TestMediaApp/assets/media_items/album_art/nature/art_nature_128.json
index 0fdd629..67200e8 100644
--- a/TestMediaApp/assets/media_items/album_art/nature/art_nature_128.json
+++ b/TestMediaApp/assets/media_items/album_art/nature/art_nature_128.json
@@ -13,7 +13,7 @@
         "MEDIA_ID": "art_nature_128 bee",
         "DISPLAY_TITLE": "Bee",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-128/bee.jpg"
+        "ART_URI": "assets/bitmaps/nature-128/bee.jpg"
       }
     },
     {
@@ -22,7 +22,7 @@
         "MEDIA_ID": "art_nature_128 clouds",
         "DISPLAY_TITLE": "Clouds",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-128/clouds.jpg"
+        "ART_URI": "assets/bitmaps/nature-128/clouds.jpg"
       }
     },
     {
@@ -31,7 +31,7 @@
         "MEDIA_ID": "art_nature_128 flower1",
         "DISPLAY_TITLE": "Flower 1",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-128/flower1.jpg"
+        "ART_URI": "assets/bitmaps/nature-128/flower1.jpg"
       }
     },
     {
@@ -40,7 +40,7 @@
         "MEDIA_ID": "art_nature_128 flower2",
         "DISPLAY_TITLE": "Flower 2",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-128/flower2.jpg"
+        "ART_URI": "assets/bitmaps/nature-128/flower2.jpg"
       }
     },
     {
@@ -49,7 +49,7 @@
         "MEDIA_ID": "art_nature_128 flower3",
         "DISPLAY_TITLE": "Flower3 ",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-128/flower3.jpg"
+        "ART_URI": "assets/bitmaps/nature-128/flower3.jpg"
       }
     },
     {
@@ -58,7 +58,7 @@
         "MEDIA_ID": "art_nature_128 flowers",
         "DISPLAY_TITLE": "Flowers",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-128/flowers.jpg"
+        "ART_URI": "assets/bitmaps/nature-128/flowers.jpg"
       }
     },
     {
@@ -67,7 +67,7 @@
         "MEDIA_ID": "art_nature_128 leaves",
         "DISPLAY_TITLE": "Leaves",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-128/leaves.jpg"
+        "ART_URI": "assets/bitmaps/nature-128/leaves.jpg"
       }
     },
     {
@@ -76,7 +76,7 @@
         "MEDIA_ID": "art_nature_128 sage",
         "DISPLAY_TITLE": "Sage",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-128/sage.jpg"
+        "ART_URI": "assets/bitmaps/nature-128/sage.jpg"
       }
     },
     {
@@ -85,7 +85,7 @@
         "MEDIA_ID": "art_nature_128 tree",
         "DISPLAY_TITLE": "Tree",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-128/tree.jpg"
+        "ART_URI": "assets/bitmaps/nature-128/tree.jpg"
       }
     }
   ]
diff --git a/TestMediaApp/assets/media_items/album_art/nature/art_nature_256.json b/TestMediaApp/assets/media_items/album_art/nature/art_nature_256.json
index 200ecc1..719665e 100644
--- a/TestMediaApp/assets/media_items/album_art/nature/art_nature_256.json
+++ b/TestMediaApp/assets/media_items/album_art/nature/art_nature_256.json
@@ -13,7 +13,7 @@
         "MEDIA_ID": "art_nature_256 bee",
         "DISPLAY_TITLE": "Bee",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-256/bee.jpg"
+        "ART_URI": "assets/bitmaps/nature-256/bee.jpg"
       }
     },
     {
@@ -22,7 +22,7 @@
         "MEDIA_ID": "art_nature_256 clouds",
         "DISPLAY_TITLE": "Clouds",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-256/clouds.jpg"
+        "ART_URI": "assets/bitmaps/nature-256/clouds.jpg"
       }
     },
     {
@@ -31,7 +31,7 @@
         "MEDIA_ID": "art_nature_256 flower1",
         "DISPLAY_TITLE": "Flower 1",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-256/flower1.jpg"
+        "ART_URI": "assets/bitmaps/nature-256/flower1.jpg"
       }
     },
     {
@@ -40,7 +40,7 @@
         "MEDIA_ID": "art_nature_256 flower2",
         "DISPLAY_TITLE": "Flower 2",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-256/flower2.jpg"
+        "ART_URI": "assets/bitmaps/nature-256/flower2.jpg"
       }
     },
     {
@@ -49,7 +49,7 @@
         "MEDIA_ID": "art_nature_256 flower3",
         "DISPLAY_TITLE": "Flower3 ",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-256/flower3.jpg"
+        "ART_URI": "assets/bitmaps/nature-256/flower3.jpg"
       }
     },
     {
@@ -58,7 +58,7 @@
         "MEDIA_ID": "art_nature_256 flowers",
         "DISPLAY_TITLE": "Flowers",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-256/flowers.jpg"
+        "ART_URI": "assets/bitmaps/nature-256/flowers.jpg"
       }
     },
     {
@@ -67,7 +67,7 @@
         "MEDIA_ID": "art_nature_256 leaves",
         "DISPLAY_TITLE": "Leaves",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-256/leaves.jpg"
+        "ART_URI": "assets/bitmaps/nature-256/leaves.jpg"
       }
     },
     {
@@ -76,7 +76,7 @@
         "MEDIA_ID": "art_nature_256 sage",
         "DISPLAY_TITLE": "Sage",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-256/sage.jpg"
+        "ART_URI": "assets/bitmaps/nature-256/sage.jpg"
       }
     },
     {
@@ -85,7 +85,7 @@
         "MEDIA_ID": "art_nature_256 tree",
         "DISPLAY_TITLE": "Tree",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-256/tree.jpg"
+        "ART_URI": "assets/bitmaps/nature-256/tree.jpg"
       }
     }
   ]
diff --git a/TestMediaApp/assets/media_items/album_art/nature/art_nature_512.json b/TestMediaApp/assets/media_items/album_art/nature/art_nature_512.json
index 56bb6a4..29cb783 100644
--- a/TestMediaApp/assets/media_items/album_art/nature/art_nature_512.json
+++ b/TestMediaApp/assets/media_items/album_art/nature/art_nature_512.json
@@ -13,7 +13,7 @@
         "MEDIA_ID": "art_nature_512 bee",
         "DISPLAY_TITLE": "Bee",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-512/bee.jpg"
+        "ART_URI": "assets/bitmaps/nature-512/bee.jpg"
       }
     },
     {
@@ -22,7 +22,7 @@
         "MEDIA_ID": "art_nature_512 clouds",
         "DISPLAY_TITLE": "Clouds",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-512/clouds.jpg"
+        "ART_URI": "assets/bitmaps/nature-512/clouds.jpg"
       }
     },
     {
@@ -31,7 +31,7 @@
         "MEDIA_ID": "art_nature_512 flower1",
         "DISPLAY_TITLE": "Flower 1",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-512/flower1.jpg"
+        "ART_URI": "assets/bitmaps/nature-512/flower1.jpg"
       }
     },
     {
@@ -40,7 +40,7 @@
         "MEDIA_ID": "art_nature_512 flower2",
         "DISPLAY_TITLE": "Flower 2",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-512/flower2.jpg"
+        "ART_URI": "assets/bitmaps/nature-512/flower2.jpg"
       }
     },
     {
@@ -49,7 +49,7 @@
         "MEDIA_ID": "art_nature_512 flower3",
         "DISPLAY_TITLE": "Flower3 ",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-512/flower3.jpg"
+        "ART_URI": "assets/bitmaps/nature-512/flower3.jpg"
       }
     },
     {
@@ -58,7 +58,7 @@
         "MEDIA_ID": "art_nature_512 flowers",
         "DISPLAY_TITLE": "Flowers",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-512/flowers.jpg"
+        "ART_URI": "assets/bitmaps/nature-512/flowers.jpg"
       }
     },
     {
@@ -67,7 +67,7 @@
         "MEDIA_ID": "art_nature_512 leaves",
         "DISPLAY_TITLE": "Leaves",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-512/leaves.jpg"
+        "ART_URI": "assets/bitmaps/nature-512/leaves.jpg"
       }
     },
     {
@@ -76,7 +76,7 @@
         "MEDIA_ID": "art_nature_512 sage",
         "DISPLAY_TITLE": "Sage",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-512/sage.jpg"
+        "ART_URI": "assets/bitmaps/nature-512/sage.jpg"
       }
     },
     {
@@ -85,7 +85,7 @@
         "MEDIA_ID": "art_nature_512 tree",
         "DISPLAY_TITLE": "Tree",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-512/tree.jpg"
+        "ART_URI": "assets/bitmaps/nature-512/tree.jpg"
       }
     }
   ]
diff --git a/TestMediaApp/assets/media_items/album_art/nature/art_nature_64.json b/TestMediaApp/assets/media_items/album_art/nature/art_nature_64.json
index 913dd07..72a3f41 100644
--- a/TestMediaApp/assets/media_items/album_art/nature/art_nature_64.json
+++ b/TestMediaApp/assets/media_items/album_art/nature/art_nature_64.json
@@ -13,7 +13,7 @@
         "MEDIA_ID": "art_nature_64 bee",
         "DISPLAY_TITLE": "Bee",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-64/bee.jpg"
+        "ART_URI": "assets/bitmaps/nature-64/bee.jpg"
       }
     },
     {
@@ -22,7 +22,7 @@
         "MEDIA_ID": "art_nature_64 clouds",
         "DISPLAY_TITLE": "Clouds",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-64/clouds.jpg"
+        "ART_URI": "assets/bitmaps/nature-64/clouds.jpg"
       }
     },
     {
@@ -31,7 +31,7 @@
         "MEDIA_ID": "art_nature_64 flower1",
         "DISPLAY_TITLE": "Flower 1",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-64/flower1.jpg"
+        "ART_URI": "assets/bitmaps/nature-64/flower1.jpg"
       }
     },
     {
@@ -40,7 +40,7 @@
         "MEDIA_ID": "art_nature_64 flower2",
         "DISPLAY_TITLE": "Flower 2",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-64/flower2.jpg"
+        "ART_URI": "assets/bitmaps/nature-64/flower2.jpg"
       }
     },
     {
@@ -49,7 +49,7 @@
         "MEDIA_ID": "art_nature_64 flower3",
         "DISPLAY_TITLE": "Flower3 ",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-64/flower3.jpg"
+        "ART_URI": "assets/bitmaps/nature-64/flower3.jpg"
       }
     },
     {
@@ -58,7 +58,7 @@
         "MEDIA_ID": "art_nature_64 flowers",
         "DISPLAY_TITLE": "Flowers",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-64/flowers.jpg"
+        "ART_URI": "assets/bitmaps/nature-64/flowers.jpg"
       }
     },
     {
@@ -67,7 +67,7 @@
         "MEDIA_ID": "art_nature_64 leaves",
         "DISPLAY_TITLE": "Leaves",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-64/leaves.jpg"
+        "ART_URI": "assets/bitmaps/nature-64/leaves.jpg"
       }
     },
     {
@@ -76,7 +76,7 @@
         "MEDIA_ID": "art_nature_64 sage",
         "DISPLAY_TITLE": "Sage",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-64/sage.jpg"
+        "ART_URI": "assets/bitmaps/nature-64/sage.jpg"
       }
     },
     {
@@ -85,7 +85,7 @@
         "MEDIA_ID": "art_nature_64 tree",
         "DISPLAY_TITLE": "Tree",
         "DURATION": 10000,
-        "ART_URI": "bitmaps/nature-64/tree.jpg"
+        "ART_URI": "assets/bitmaps/nature-64/tree.jpg"
       }
     }
   ]
diff --git a/TestMediaApp/assets/media_items/album_art/nature/art_nature_files.json b/TestMediaApp/assets/media_items/album_art/nature/art_nature_files.json
new file mode 100644
index 0000000..2643829
--- /dev/null
+++ b/TestMediaApp/assets/media_items/album_art/nature/art_nature_files.json
@@ -0,0 +1,92 @@
+{
+  "FLAGS": "browsable",
+
+  "METADATA": {
+    "MEDIA_ID": "art_nature_files_leaves",
+    "DISPLAY_TITLE": "Art nature files"
+  },
+
+  "CHILDREN": [
+    {
+      "FLAGS": "playable",
+      "METADATA": {
+        "MEDIA_ID": "art_nature_files_leaves bee",
+        "DISPLAY_TITLE": "Bee",
+        "DURATION": 10000,
+        "ART_URI": "files/bitmaps/nature-1024/bee.jpg"
+      }
+    },
+    {
+      "FLAGS": "playable",
+      "METADATA": {
+        "MEDIA_ID": "art_nature_files_leaves clouds",
+        "DISPLAY_TITLE": "Clouds",
+        "DURATION": 10000,
+        "ART_URI": "files/bitmaps/nature-1024/clouds.jpg"
+      }
+    },
+    {
+      "FLAGS": "playable",
+      "METADATA": {
+        "MEDIA_ID": "art_nature_files_leaves flower1",
+        "DISPLAY_TITLE": "Flower 1",
+        "DURATION": 10000,
+        "ART_URI": "files/bitmaps/nature-1024/flower1.jpg"
+      }
+    },
+    {
+      "FLAGS": "playable",
+      "METADATA": {
+        "MEDIA_ID": "art_nature_files_leaves flower2",
+        "DISPLAY_TITLE": "Flower 2",
+        "DURATION": 10000,
+        "ART_URI": "files/bitmaps/nature-1024/flower2.jpg"
+      }
+    },
+    {
+      "FLAGS": "playable",
+      "METADATA": {
+        "MEDIA_ID": "art_nature_files_leaves flower3",
+        "DISPLAY_TITLE": "Flower3 ",
+        "DURATION": 10000,
+        "ART_URI": "files/bitmaps/nature-1024/flower3.jpg"
+      }
+    },
+    {
+      "FLAGS": "playable",
+      "METADATA": {
+        "MEDIA_ID": "art_nature_files_leaves flowers",
+        "DISPLAY_TITLE": "Flowers",
+        "DURATION": 10000,
+        "ART_URI": "files/bitmaps/nature-1024/flowers.jpg"
+      }
+    },
+    {
+      "FLAGS": "playable",
+      "METADATA": {
+        "MEDIA_ID": "art_nature_files_leaves leaves",
+        "DISPLAY_TITLE": "Leaves",
+        "DURATION": 10000,
+        "ART_URI": "files/bitmaps/nature-1024/leaves.jpg"
+      }
+    },
+    {
+      "FLAGS": "playable",
+      "METADATA": {
+        "MEDIA_ID": "art_nature_files_leaves sage",
+        "DISPLAY_TITLE": "Sage",
+        "DURATION": 10000,
+        "ART_URI": "files/bitmaps/nature-1024/sage.jpg"
+      }
+    },
+    {
+      "FLAGS": "playable",
+      "METADATA": {
+        "MEDIA_ID": "art_nature_files_leaves tree",
+        "DISPLAY_TITLE": "Tree",
+        "DURATION": 10000,
+        "ART_URI": "files/bitmaps/nature-1024/tree.jpg"
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/TestMediaApp/assets/media_items/mixed.json b/TestMediaApp/assets/media_items/mixed.json
index 7e04e85..34da9c9 100644
--- a/TestMediaApp/assets/media_items/mixed.json
+++ b/TestMediaApp/assets/media_items/mixed.json
@@ -1,7 +1,7 @@
 {
   "FLAGS": "browsable",
   "PLAYABLE_HINT": "GRID",
-  "BROWSABLE_HINT": "LIST",
+  "BROWSABLE_HINT": "LIST_CATEGORY",
 
   "METADATA": {
     "MEDIA_ID": "mixed",
diff --git a/TestMediaApp/assets/media_items/only_nodes.json b/TestMediaApp/assets/media_items/only_nodes.json
index a020abd..26f998e 100644
--- a/TestMediaApp/assets/media_items/only_nodes.json
+++ b/TestMediaApp/assets/media_items/only_nodes.json
@@ -32,7 +32,7 @@
     {
       "FLAGS": "browsable",
       "PLAYABLE_HINT": "GRID",
-      "BROWSABLE_HINT": "LIST",
+      "BROWSABLE_HINT": "LIST_CATEGORY",
       "METADATA": {
         "MEDIA_ID": "only_nodes rabbit hole",
         "DISPLAY_TITLE": "Rabbit hole 2"
diff --git a/TestMediaApp/assets/media_items/simple_leaves.json b/TestMediaApp/assets/media_items/simple_leaves.json
index e666c1b..dc6b0a3 100644
--- a/TestMediaApp/assets/media_items/simple_leaves.json
+++ b/TestMediaApp/assets/media_items/simple_leaves.json
@@ -11,7 +11,7 @@
       "FLAGS": "playable",
       "METADATA": {
         "MEDIA_ID": "simple_leaves normal 10s song",
-        "DISPLAY_TITLE": "A normal 10s song",
+        "DISPLAY_TITLE": "A normal 10s song with a long title. A normal 10s song with a long title. A normal 10s song with a long title. ",
         "DURATION": 10000
       }
     },
@@ -20,6 +20,8 @@
       "METADATA": {
         "MEDIA_ID": "simple_leaves normal 1H song",
         "DISPLAY_TITLE": "A normal 1H song",
+        "ARTIST": "Artist",
+        "ALBUM":"Album",
         "DURATION": 3600000
       }
     },
@@ -28,6 +30,9 @@
       "METADATA": {
         "MEDIA_ID": "simple_leaves slow connection",
         "DISPLAY_TITLE": "Connects and buffers for 4s each",
+        "DISPLAY_SUBTITLE": "A very long subtitle. A very long subtitle. A very long subtitle. A very long subtitle. A very long subtitle. A very long subtitle. ",
+        "ARTIST": "This is a very long artist name. This is a very long artist name. This is a very long artist name.",
+        "ALBUM":"Album",
         "DURATION": 30000
       },
       "EVENTS": [
@@ -41,6 +46,7 @@
       "METADATA": {
         "MEDIA_ID": "simple_leaves poor internet",
         "DISPLAY_TITLE": "Poor internet quality at 2s",
+        "ARTIST": "Artist",
         "DURATION": 30000
       },
       "EVENTS": [
@@ -58,6 +64,8 @@
       "METADATA": {
         "MEDIA_ID": "simple_leaves cache failure",
         "DISPLAY_TITLE": "Caching failure at 2s",
+        "DISPLAY_SUBTITLE": "Show a toast",
+        "ALBUM":"This is a very long album title. This is a very long album title. This is a very long album title.",
         "DURATION": 30000
       },
       "EVENTS": [
@@ -75,6 +83,7 @@
       "METADATA": {
         "MEDIA_ID": "simple_leaves error code",
         "DISPLAY_TITLE": "Parental Control error code at 1s",
+        "DISPLAY_SUBTITLE": "Show a toast",
         "DURATION": 10000
       },
       "EVENTS": [
@@ -91,6 +100,7 @@
       "METADATA": {
         "MEDIA_ID": "simple_leaves premium required",
         "DISPLAY_TITLE": "Paid account required at 1s",
+        "DISPLAY_SUBTITLE": "Show a dialog",
         "DURATION": 50000
       },
       "EVENTS": [
diff --git a/TestMediaApp/assets/media_items/single_node.json b/TestMediaApp/assets/media_items/single_node.json
new file mode 100644
index 0000000..cf93cee
--- /dev/null
+++ b/TestMediaApp/assets/media_items/single_node.json
@@ -0,0 +1,22 @@
+{
+  "FLAGS": "browsable",
+  "PLAYABLE_HINT": "LIST",
+  "BROWSABLE_HINT": "GRID",
+
+  "METADATA": {
+    "MEDIA_ID": "single_node",
+    "DISPLAY_TITLE": "A lonely tab"
+  },
+
+  "CHILDREN": [
+    {
+      "FLAGS": "browsable",
+      "METADATA": {
+        "MEDIA_ID": "single_node simple_leaves",
+        "DISPLAY_TITLE": "Basic songs",
+        "ART_URI": "drawable/ic_heart_plus_plus"
+      },
+      "INCLUDE":"media_items/simple_leaves.json"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/TestMediaApp/assets/media_items/untagged.json b/TestMediaApp/assets/media_items/untagged.json
new file mode 100644
index 0000000..fd8f5f5
--- /dev/null
+++ b/TestMediaApp/assets/media_items/untagged.json
@@ -0,0 +1,27 @@
+{
+  "FLAGS": "browsable",
+
+  "METADATA": {
+    "MEDIA_ID": "untagged",
+    "DISPLAY_TITLE": "Untagged media items"
+  },
+
+  "CHILDREN": [
+    {
+      "METADATA": {
+        "MEDIA_ID": "untagged normal 10s song",
+        "DISPLAY_TITLE": "A normal 10s song with a long title. A normal 10s song with a long title. A normal 10s song with a long title. ",
+        "DURATION": 10000
+      }
+    },
+    {
+      "METADATA": {
+        "MEDIA_ID": "untagged normal 1H song",
+        "DISPLAY_TITLE": "A normal 1H song",
+        "ARTIST": "Artist",
+        "ALBUM":"Album",
+        "DURATION": 3600000
+      }
+    }
+  ]
+}
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/MediaKeys.java b/TestMediaApp/src/com/android/car/media/testmediaapp/MediaKeys.java
index 8506b6b..9fba1a8 100644
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/MediaKeys.java
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/MediaKeys.java
@@ -22,6 +22,10 @@
  */
 public class MediaKeys {
 
+    /** Integer extra indicating the recommended size (in pixels) for media art bitmaps. */
+    public static final String EXTRA_MEDIA_ART_SIZE_HINT_PIXELS =
+            "android.media.extras.MEDIA_ART_SIZE_HINT_PIXELS";
+
     /**
      * Bundle extra holding the Pending Intent to launch to let users resolve the current error.
      * See {@link #ERROR_RESOLUTION_ACTION_LABEL} for more details.
@@ -61,4 +65,18 @@
      * hints the corresponding items should be presented as grids.
      */
     static final int CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2;
+
+    /**
+     * Value for {@link #CONTENT_STYLE_BROWSABLE_HINT} that hints the corresponding items should be
+     * presented as a "category" list, where media items are browsable and represented by a
+     * meaningful icon.
+     */
+    public static final int CONTENT_STYLE_CATEGORY_LIST_ITEM_HINT_VALUE = 3;
+
+    /**
+     * Value for {@link #CONTENT_STYLE_BROWSABLE_HINT} that hints the corresponding items should be
+     * presented as a "category" grid, where media items are browsable and represented by a
+     * meaningful icon.
+     */
+    public static final int CONTENT_STYLE_CATEGORY_GRID_ITEM_HINT_VALUE = 4;
 }
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaAssetProvider.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaAssetProvider.java
deleted file mode 100644
index fc9fd49..0000000
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaAssetProvider.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.car.media.testmediaapp;
-
-import android.content.ContentProvider;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.res.AssetFileDescriptor;
-import android.database.Cursor;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.car.media.testmediaapp.prefs.TmaPrefs;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
-public class TmaAssetProvider extends ContentProvider {
-
-    private static final String TAG = "TmaAssetProvider";
-
-    private static final String PACKAGE_NAME = "com.android.car.media.testmediaapp";
-
-    private static final String ASSET_URI_PREFIX =
-            ContentResolver.SCHEME_CONTENT + "://" + PACKAGE_NAME + ".assets/";
-
-    private static final String RESOURCE_URI_PREFIX =
-            ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + PACKAGE_NAME + "/";
-
-
-    public static String buildUriString(String localArt) {
-        String prefix = localArt.startsWith("drawable") ? RESOURCE_URI_PREFIX : ASSET_URI_PREFIX;
-        return prefix + localArt;
-    }
-
-    private int mAssetDelay = 0;
-
-    @Override
-    public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
-        Log.i(TAG, "TmaAssetProvider#openAssetFile " + uri);
-
-        try {
-            Thread.sleep(mAssetDelay + (int)(mAssetDelay * (Math.random())));
-        } catch (InterruptedException ignored) {
-        }
-
-        String file_path = uri.getPath();
-        if (TextUtils.isEmpty(file_path)) throw new FileNotFoundException();
-        try {
-            if (file_path.startsWith("/")) {
-                file_path = file_path.substring(1);
-            }
-            return getContext().getAssets().openFd(file_path);
-        } catch (IOException e) {
-            Log.e(TAG, "openAssetFile failed: " + e);
-            return null;
-        }
-    }
-
-    @Override
-    public boolean onCreate() {
-        TmaPrefs.getInstance(getContext()).mAssetReplyDelay.registerChangeListener(
-                (oldValue, newValue) -> mAssetDelay = newValue.mReplyDelayMs);
-        return true;
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
-        return null;
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        return null;
-    }
-
-    @Override
-    public Uri insert(Uri uri, ContentValues values) {
-        return null;
-    }
-
-    @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
-        return 0;
-    }
-
-    @Override
-    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        return 0;
-    }
-}
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaBrowser.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaBrowser.java
index 02e8292..7a62137 100644
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaBrowser.java
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaBrowser.java
@@ -26,6 +26,7 @@
 import android.support.v4.media.MediaBrowserCompat.MediaItem;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -38,6 +39,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 
 /**
@@ -49,10 +52,17 @@
  * {@link TmaPlayer}.
  */
 public class TmaBrowser extends MediaBrowserServiceCompat {
+    private static final String TAG = "TmaBrowser";
 
+    private static final int MAX_SEARCH_DEPTH = 4;
     private static final String MEDIA_SESSION_TAG = "TEST_MEDIA_SESSION";
     private static final String ROOT_ID = "_ROOT_ID_";
     private static final String SEARCH_SUPPORTED = "android.media.browse.SEARCH_SUPPORTED";
+    /**
+     * Extras key to allow Android Auto to identify the browse service from the media session.
+     */
+    private static final String BROWSE_SERVICE_FOR_SESSION_KEY =
+        "android.media.session.BROWSE_SERVICE";
 
     private TmaPrefs mPrefs;
     private Handler mHandler;
@@ -61,7 +71,6 @@
     private TmaPlayer mPlayer;
 
     private BrowserRoot mRoot;
-    private String mLastLoadedNodeId;
 
     @Override
     public void onCreate() {
@@ -78,6 +87,9 @@
         mSession.setCallback(mPlayer);
         mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
                 | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
+        Bundle mediaSessionExtras = new Bundle();
+        mediaSessionExtras.putString(BROWSE_SERVICE_FOR_SESSION_KEY, TmaBrowser.class.getName());
+        mSession.setExtras(mediaSessionExtras);
 
         mPrefs.mAccountType.registerChangeListener(
                 (oldValue, newValue) -> onAccountChanged(newValue));
@@ -88,9 +100,11 @@
         mPrefs.mRootReplyDelay.registerChangeListener(
                 (oldValue, newValue) -> invalidateRoot());
 
-        Bundle extras = new Bundle();
-        extras.putBoolean(SEARCH_SUPPORTED, true);
-        mRoot = new BrowserRoot(ROOT_ID, extras);
+        Bundle browserRootExtras = new Bundle();
+        browserRootExtras.putBoolean(SEARCH_SUPPORTED, true);
+        mRoot = new BrowserRoot(ROOT_ID, browserRootExtras);
+
+        updatePlaybackState(mPrefs.mAccountType.getValue());
     }
 
     @Override
@@ -115,6 +129,8 @@
 
     private void updatePlaybackState(TmaAccountType accountType) {
         if (accountType == TmaAccountType.NONE) {
+            mSession.setMetadata(null);
+            mPlayer.onStop();
             mPlayer.setPlaybackState(
                     new TmaMediaEvent(TmaMediaEvent.EventState.ERROR,
                             TmaMediaEvent.StateErrorCode.AUTHENTICATION_EXPIRED,
@@ -126,6 +142,7 @@
             // TODO don't reset error in all cases...
             PlaybackStateCompat.Builder playbackState = new PlaybackStateCompat.Builder();
             playbackState.setState(PlaybackStateCompat.STATE_PAUSED, 0, 0);
+            playbackState.setActions(PlaybackStateCompat.ACTION_PREPARE);
             mSession.setPlaybackState(playbackState.build());
         }
     }
@@ -137,12 +154,17 @@
     @Override
     public BrowserRoot onGetRoot(
             @NonNull String clientPackageName, int clientUid, Bundle rootHints) {
+        if (rootHints == null) {
+            Log.e(TAG, "Client " + clientPackageName + " didn't set rootHints.");
+            throw new NullPointerException("rootHints is null");
+        }
+        Log.i(TAG, "onGetroot client: " + clientPackageName + " EXTRA_MEDIA_ART_SIZE_HINT_PIXELS: "
+                + rootHints.getInt(MediaKeys.EXTRA_MEDIA_ART_SIZE_HINT_PIXELS, 0));
         return mRoot;
     }
 
     @Override
     public void onLoadChildren(@NonNull String parentId, @NonNull Result<List<MediaItem>> result) {
-        mLastLoadedNodeId = parentId;
         getMediaItemsWithDelay(parentId, result, null);
 
         if (QUEUE_ONLY.equals(mPrefs.mRootNodeType.getValue()) && ROOT_ID.equals(parentId)) {
@@ -155,8 +177,9 @@
     }
 
     @Override
-    public void onSearch(final String query, final Bundle extras, Result<List<MediaItem>> result) {
-        getMediaItemsWithDelay(mLastLoadedNodeId, result, query);
+    public void onSearch(@NonNull String query, Bundle extras,
+            @NonNull Result<List<MediaItem>> result) {
+        getMediaItemsWithDelay(ROOT_ID, result, query);
     }
 
     private void getMediaItemsWithDelay(@NonNull String parentId,
@@ -175,14 +198,15 @@
 
             if (node == null) {
                 result.sendResult(null);
+            } else if (filter != null) {
+                List<MediaItem> hits = new ArrayList<>(50);
+                Pattern pat = Pattern.compile(Pattern.quote(filter), Pattern.CASE_INSENSITIVE);
+                addSearchResults(node, pat.matcher(""), hits, MAX_SEARCH_DEPTH);
+                result.sendResult(hits);
             } else {
                 List<MediaItem> items = new ArrayList<>(node.mChildren.size());
                 for (TmaMediaItem child : node.mChildren) {
-                    MediaItem item = child.toMediaItem();
-                    CharSequence title = item.getDescription().getTitle();
-                    if (filter == null || (title != null && title.toString().contains(filter))) {
-                        items.add(item);
-                    }
+                    items.add(child.toMediaItem());
                 }
                 result.sendResult(items);
             }
@@ -194,4 +218,26 @@
             mHandler.postDelayed(task, delay.mReplyDelayMs);
         }
     }
+
+    private void addSearchResults(@Nullable TmaMediaItem node, Matcher matcher,
+            List<MediaItem> hits, int currentDepth) {
+        if (node == null || currentDepth <= 0) {
+            return;
+        }
+
+        for (TmaMediaItem child : node.mChildren) {
+            MediaItem item = child.toMediaItem();
+            CharSequence title = item.getDescription().getTitle();
+            if (title != null) {
+                matcher.reset(title);
+                if (matcher.find()) {
+                    hits.add(item);
+                }
+            }
+
+            // Ask the library to load the grand children
+            child = mLibrary.getMediaItemById(child.getMediaId());
+            addSearchResults(child, matcher, hits, currentDepth - 1);
+        }
+    }
 }
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaLibrary.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaLibrary.java
index 27b8a7a..327a2b6 100644
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaLibrary.java
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaLibrary.java
@@ -49,9 +49,11 @@
         mRootAssetPaths.put(TmaBrowseNodeType.NULL, null);
         mRootAssetPaths.put(TmaBrowseNodeType.EMPTY, "media_items/empty.json");
         mRootAssetPaths.put(TmaBrowseNodeType.QUEUE_ONLY, "media_items/empty.json");
+        mRootAssetPaths.put(TmaBrowseNodeType.SINGLE_TAB, "media_items/single_node.json");
         mRootAssetPaths.put(TmaBrowseNodeType.NODE_CHILDREN, "media_items/only_nodes.json");
         mRootAssetPaths.put(TmaBrowseNodeType.LEAF_CHILDREN, "media_items/simple_leaves.json");
         mRootAssetPaths.put(TmaBrowseNodeType.MIXED_CHILDREN, "media_items/mixed.json");
+        mRootAssetPaths.put(TmaBrowseNodeType.UNTAGGED, "media_items/untagged.json");
     }
 
     @Nullable
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaItem.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaItem.java
index f79e273..af1b2e3 100644
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaItem.java
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaItem.java
@@ -42,7 +42,9 @@
     public enum ContentStyle {
         NONE (0),
         LIST (MediaKeys.CONTENT_STYLE_LIST_ITEM_HINT_VALUE),
-        GRID (MediaKeys.CONTENT_STYLE_GRID_ITEM_HINT_VALUE);
+        GRID (MediaKeys.CONTENT_STYLE_GRID_ITEM_HINT_VALUE),
+        LIST_CATEGORY(MediaKeys.CONTENT_STYLE_CATEGORY_LIST_ITEM_HINT_VALUE),
+        GRID_CATEGORY(MediaKeys.CONTENT_STYLE_CATEGORY_GRID_ITEM_HINT_VALUE);
         final int mBundleValue;
         ContentStyle(int value) {
             mBundleValue = value;
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java
index 2938a37..d8fab6c 100644
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java
@@ -21,6 +21,7 @@
 import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PAUSE;
 import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY;
 import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID;
+import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE;
 import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SEEK_TO;
 import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
 import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
@@ -345,7 +346,8 @@
     }
 
     private long addActions(long actions) {
-        actions |= ACTION_PLAY_FROM_MEDIA_ID | ACTION_SKIP_TO_QUEUE_ITEM | ACTION_SEEK_TO;
+        actions |= ACTION_PLAY_FROM_MEDIA_ID | ACTION_SKIP_TO_QUEUE_ITEM | ACTION_SEEK_TO
+                | ACTION_PREPARE;
 
         if (mActiveItem != null) {
             if (mActiveItem.getNext() != null) {
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPublicProvider.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPublicProvider.java
new file mode 100644
index 0000000..e7eb31b
--- /dev/null
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPublicProvider.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.media.testmediaapp;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.car.media.testmediaapp.prefs.TmaPrefs;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class TmaPublicProvider extends ContentProvider {
+
+    private static final String TAG = "TmaAssetProvider";
+
+    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+
+    private static final String AUTHORITY = "com.android.car.media.testmediaapp.public";
+
+    private static final String FILES = "/files/";
+    private static final String ASSETS = "/assets/";
+
+    private static final String CONTENT_URI_PREFIX =
+            ContentResolver.SCHEME_CONTENT + "://" + AUTHORITY + "/";
+
+    private static final String RESOURCE_URI_PREFIX =
+            ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + AUTHORITY + "/";
+
+
+    public static String buildUriString(String localArt) {
+        String prefix = localArt.startsWith("drawable") ? RESOURCE_URI_PREFIX : CONTENT_URI_PREFIX;
+        return prefix + localArt;
+    }
+
+    private int mAssetDelay = 0;
+
+
+    @Override
+    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+        String path = uri.getPath();
+
+        if (TextUtils.isEmpty(path) || !path.startsWith(FILES)) {
+            throw new FileNotFoundException(path);
+        }
+
+        Log.i(TAG, "TmaAssetProvider#openFile uri: " + uri + " path: " + path);
+
+        File localFile = new File(getContext().getFilesDir(), path);
+        if (!localFile.exists()) {
+            downloadFile(localFile, path.substring(FILES.length()));
+        }
+
+        return ParcelFileDescriptor.open(localFile,ParcelFileDescriptor.MODE_READ_ONLY);
+    }
+
+    @Override
+    public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
+        String path = uri.getPath();
+        if (TextUtils.isEmpty(path) || !path.startsWith(ASSETS)) {
+            // The ImageDecoder and media center code always try to open as asset first, but
+            // super delegates to openFile...
+            return super.openAssetFile(uri, mode);
+        }
+
+        Log.i(TAG, "TmaAssetProvider#openAssetFile uri: " + uri + " path: " + path);
+
+        try {
+            Thread.sleep(mAssetDelay + (int)(mAssetDelay * (Math.random())));
+        } catch (InterruptedException ignored) {
+        }
+
+        try {
+            return getContext().getAssets().openFd(path.substring(ASSETS.length()));
+        } catch (IOException e) {
+            Log.e(TAG, "openAssetFile failed: " + e);
+            return null;
+        }
+    }
+
+    private void downloadFile(File localFile, String assetsPath) {
+        try {
+            localFile.getParentFile().mkdirs();
+
+            InputStream input = getContext().getAssets().open(assetsPath);
+            OutputStream output = new FileOutputStream(localFile);
+
+            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
+            int n;
+            while (-1 != (n = input.read(buffer))) {
+                output.write(buffer, 0, n);
+            }
+
+        } catch (IOException e) {
+            Log.e(TAG, "downloadFile failed: " + e);
+        }
+    }
+
+    @Override
+    public boolean onCreate() {
+        TmaPrefs.getInstance(getContext()).mAssetReplyDelay.registerChangeListener(
+                (oldValue, newValue) -> mAssetDelay = newValue.mReplyDelayMs);
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaMetadataReader.java b/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaMetadataReader.java
index 5a4a217..8cc4843 100644
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaMetadataReader.java
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaMetadataReader.java
@@ -53,7 +53,7 @@
 import android.support.v4.media.MediaMetadataCompat;
 import android.util.Log;
 
-import com.android.car.media.testmediaapp.TmaAssetProvider;
+import com.android.car.media.testmediaapp.TmaPublicProvider;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -153,7 +153,7 @@
                     case TEXT:
                         String value = object.getString(jsonKey);
                         if (mUriKeys.contains(key)) {
-                            value = TmaAssetProvider.buildUriString(value);
+                            value = TmaPublicProvider.buildUriString(value);
                         }
                         builder.putString(key.mLongName, value);
                         break;
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/phone/TmaLauncherActivity.java b/TestMediaApp/src/com/android/car/media/testmediaapp/phone/TmaLauncherActivity.java
index 9ae6a6d..84f481f 100644
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/phone/TmaLauncherActivity.java
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/phone/TmaLauncherActivity.java
@@ -10,6 +10,7 @@
 
 import androidx.appcompat.app.AppCompatActivity;
 
+import com.android.car.media.testmediaapp.MediaKeys;
 import com.android.car.media.testmediaapp.TmaBrowser;
 import com.android.car.media.testmediaapp.prefs.TmaPrefsActivity;
 import com.android.car.media.testmediaapp.R;
@@ -37,9 +38,11 @@
             startActivity(prefsIntent);
         });
 
-
+        Bundle rootHints = new Bundle();
+        // TODO: 256 is just a placeholder. We'd better find a proper value.
+        rootHints.putInt(MediaKeys.EXTRA_MEDIA_ART_SIZE_HINT_PIXELS, 256);
         mediaBrowser = new MediaBrowserCompat(this, new ComponentName(this, TmaBrowser.class),
-                mConnectionCallbacks, null);
+                mConnectionCallbacks, rootHints);
     }
 
     private final MediaBrowserCompat.ConnectionCallback mConnectionCallbacks =
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaEnumPrefs.java b/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaEnumPrefs.java
index 744ef01..3702929 100644
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaEnumPrefs.java
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaEnumPrefs.java
@@ -84,9 +84,11 @@
         NULL("Null (error)", "null"),
         EMPTY("Empty", "empty"),
         QUEUE_ONLY("Queue only", "queue-only"),
+        SINGLE_TAB("Single browse-able tab", "single-tab"),
         NODE_CHILDREN("Only browse-able content", "nodes"),
         LEAF_CHILDREN("Only playable content (basic working and error cases)", "leaves"),
-        MIXED_CHILDREN("Mixed content (apps are not supposed to do that)", "mixed");
+        MIXED_CHILDREN("Mixed content (apps are not supposed to do that)", "mixed"),
+        UNTAGGED("Untagged media items (not playable or browsable)", "untagged");
 
         private final PrefValueImpl mPrefValue;