diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2019-10-28 15:29:42 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2019-10-28 15:29:42 +0000 |
commit | a829943c39b100d34e4065583b4eb67a41658675 (patch) | |
tree | aae9173350881e3380ce15f12d618e1e922719ba | |
parent | 47791d7120dfa667e3a30ff487ab11a10623d1bf (diff) | |
parent | 5cb8036a6830d6981100efaf7a6ee389264cf440 (diff) | |
download | tests-a829943c39b100d34e4065583b4eb67a41658675.tar.gz |
Snap for 5970985 from 5cb8036a6830d6981100efaf7a6ee389264cf440 to qt-d4-release
Change-Id: I29af7418650cc6b91791354ea30fdf4b7e1d8760
36 files changed, 913 insertions, 96 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd59d21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +build/ + diff --git a/TestMediaApp/Android.mk b/TestMediaApp/Android.mk index b97a07e..67127e0 100644 --- a/TestMediaApp/Android.mk +++ b/TestMediaApp/Android.mk @@ -34,11 +34,12 @@ LOCAL_CERTIFICATE := platform LOCAL_MODULE_TAGS := optional # car_car is ok here because this is meant to simulate a third party media app +# Do NOT add dependencies preventing the app from being unbundled (compiled with gradle in Studio). LOCAL_STATIC_ANDROID_LIBRARIES := \ androidx.car_car \ androidx.appcompat_appcompat \ androidx.preference_preference \ - car-media-common + androidx.legacy_legacy-support-v4 LOCAL_USE_AAPT2 := true diff --git a/TestMediaApp/AndroidManifest.xml b/TestMediaApp/AndroidManifest.xml index 859815b..911145e 100644 --- a/TestMediaApp/AndroidManifest.xml +++ b/TestMediaApp/AndroidManifest.xml @@ -17,9 +17,10 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.car.media.testmediaapp" > - <uses-sdk - android:minSdkVersion="21" - android:targetSdkVersion="28"/> + <uses-feature + android:name="android.hardware.type.automotive" + android:required="true"/> + <application android:allowBackup="true" @@ -27,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" @@ -65,6 +67,8 @@ <!-- To use the app on a phone. --> + <meta-data android:name="com.google.android.gms.car.application" + android:resource="@xml/automotive_app_desc"/> <activity android:name=".phone.TmaLauncherActivity" > <intent-filter> diff --git a/TestMediaApp/assets/media_items/album_art/art_nodes.json b/TestMediaApp/assets/media_items/album_art/art_nodes.json index 434ed23..974268b 100644 --- a/TestMediaApp/assets/media_items/album_art/art_nodes.json +++ b/TestMediaApp/assets/media_items/album_art/art_nodes.json @@ -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/simple_leaves.json b/TestMediaApp/assets/media_items/simple_leaves.json index d70a2a4..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": [ @@ -104,6 +114,30 @@ "POST_DELAY_MS": 1000 } ] + }, + { + "FLAGS": "playable", + "METADATA": { + "MEDIA_ID": "simple_leaves bluetooth disconnected and reconnected", + "DISPLAY_TITLE": "Bluetooth disconnected at 2s and reconnected at 8s", + "DURATION": 20000 + }, + "EVENTS": [ + { "STATE": "PLAYING", "POST_DELAY_MS": 0 }, + { + "STATE": "ERROR", + "ERROR_MESSAGE": "Bluetooth audio disconnected.", + "POST_DELAY_MS": 2000 + }, + { + "ACTION": "RESET_METADATA", + "POST_DELAY_MS": 6000 + }, + { + "STATE": "PLAYING", + "POST_DELAY_MS": 3000 + } + ] } ] -}
\ No newline at end of file +} 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/build.gradle b/TestMediaApp/build.gradle new file mode 100644 index 0000000..79fd66d --- /dev/null +++ b/TestMediaApp/build.gradle @@ -0,0 +1,60 @@ +/* + * 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. + */ + + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.android.car.media.testmediaapp" + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + lintOptions { + abortOnError false + } + buildTypes { + release { + minifyEnabled false + } + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src'] + aidl.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + } + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.media:media:1.0.1' + implementation 'androidx.preference:preference:1.0.0' +} diff --git a/TestMediaApp/res/drawable/button_ripple_bg.xml b/TestMediaApp/res/drawable/button_ripple_bg.xml index d012c94..9c99a25 100644 --- a/TestMediaApp/res/drawable/button_ripple_bg.xml +++ b/TestMediaApp/res/drawable/button_ripple_bg.xml @@ -17,4 +17,4 @@ <ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="@*android:color/car_card_ripple_background" /> + android:color="@color/ripple_background_color" /> diff --git a/TestMediaApp/res/drawable/ic_close.xml b/TestMediaApp/res/drawable/ic_close.xml new file mode 100644 index 0000000..f4c1e3b --- /dev/null +++ b/TestMediaApp/res/drawable/ic_close.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> + +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:width="56dp" + android:height="56dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFF" + android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/> +</vector>
\ No newline at end of file diff --git a/TestMediaApp/res/values/styles.xml b/TestMediaApp/res/values/styles.xml index cf316c5..6a8e8b1 100644 --- a/TestMediaApp/res/values/styles.xml +++ b/TestMediaApp/res/values/styles.xml @@ -16,8 +16,11 @@ */ --> <resources> - <style name="TestMediaAppTheme" parent="Theme.Car.Light.NoActionBar"> - <item name="android:windowBackground">@color/car_card_dark</item> + <style name="TestMediaAppTheme" parent="Theme.AppCompat.Light.NoActionBar"> + <item name="android:windowBackground">@color/window_background </item> </style> + <color name="window_background">#AAA</color> + <color name="ripple_background_color">#444</color> + </resources> diff --git a/TestMediaApp/res/xml/automotive_app_desc.xml b/TestMediaApp/res/xml/automotive_app_desc.xml new file mode 100644 index 0000000..3daa01a --- /dev/null +++ b/TestMediaApp/res/xml/automotive_app_desc.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. + */ +--> +<automotiveApp xmlns:android="http://schemas.android.com/apk/res/android"> + <uses name="media"/> +</automotiveApp>
\ No newline at end of file diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/MediaKeys.java b/TestMediaApp/src/com/android/car/media/testmediaapp/MediaKeys.java new file mode 100644 index 0000000..9c58483 --- /dev/null +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/MediaKeys.java @@ -0,0 +1,68 @@ +/* + * 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; + +/** + * Copy of constants defined in com.android.car.media.common.MediaConstants until they can be moved + * to a shared location available to all media apps. This makes un-bundling TestMediaApp easier. + */ +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. + */ + static final String ERROR_RESOLUTION_ACTION_INTENT = + "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT"; + + + /** + * Bundle extra indicating the label of the button users can tap to resolve an error state. + */ + static final String ERROR_RESOLUTION_ACTION_LABEL = + "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL"; + + /** + * Bundle extra indicating the presentation hint for playable media items. See {@link + * #CONTENT_STYLE_LIST_ITEM_HINT_VALUE} or {@link #CONTENT_STYLE_GRID_ITEM_HINT_VALUE} + */ + static final String CONTENT_STYLE_PLAYABLE_HINT = + "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT"; + + /** + * Bundle extra indicating the presentation hint for browsable media items. See {@link + * #CONTENT_STYLE_LIST_ITEM_HINT_VALUE} or {@link #CONTENT_STYLE_GRID_ITEM_HINT_VALUE} + */ + static final String CONTENT_STYLE_BROWSABLE_HINT = + "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT"; + + /** + * Value for {@link #CONTENT_STYLE_PLAYABLE_HINT} and {@link #CONTENT_STYLE_BROWSABLE_HINT} that + * hints the corresponding items should be presented as lists. + */ + static final int CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1; + + /** + * Value for {@link #CONTENT_STYLE_PLAYABLE_HINT} and {@link #CONTENT_STYLE_BROWSABLE_HINT} that + * hints the corresponding items should be presented as grids. + */ + static final int CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2; +} diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaBrowser.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaBrowser.java index a51e623..6aeac39 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaBrowser.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaBrowser.java @@ -15,6 +15,10 @@ */ package com.android.car.media.testmediaapp; +import static com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaBrowseNodeType.LEAF_CHILDREN; +import static com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaBrowseNodeType.QUEUE_ONLY; +import static com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaLoginEventOrder.PLAYBACK_STATE_UPDATE_FIRST; + import android.content.Context; import android.media.AudioManager; import android.os.Bundle; @@ -22,6 +26,7 @@ import android.os.Handler; 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; @@ -45,10 +50,16 @@ import java.util.List; * {@link TmaPlayer}. */ public class TmaBrowser extends MediaBrowserServiceCompat { + private static final String TAG = "TmaBrowser"; 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; @@ -74,6 +85,9 @@ public class TmaBrowser extends MediaBrowserServiceCompat { 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)); @@ -84,9 +98,11 @@ public class TmaBrowser extends MediaBrowserServiceCompat { 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 @@ -98,20 +114,35 @@ public class TmaBrowser extends MediaBrowserServiceCompat { } private void onAccountChanged(TmaAccountType accountType) { + if (PLAYBACK_STATE_UPDATE_FIRST.equals(mPrefs.mLoginEventOrder.getValue())) { + updatePlaybackState(accountType); + invalidateRoot(); + } else { + invalidateRoot(); + (new Handler()).postDelayed(() -> { + updatePlaybackState(accountType); + }, 3000); + } + } + + 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, getResources().getString(R.string.no_account), getResources().getString(R.string.select_account), - TmaMediaEvent.ResolutionIntent.PREFS, 0, null)); + TmaMediaEvent.ResolutionIntent.PREFS, + TmaMediaEvent.Action.NONE, 0, null)); } else { // 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()); } - invalidateRoot(); } private void invalidateRoot() { @@ -121,6 +152,8 @@ public class TmaBrowser extends MediaBrowserServiceCompat { @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, Bundle rootHints) { + Log.i(TAG, "onGetroot client: " + clientPackageName + " EXTRA_MEDIA_ART_SIZE_HINT_PIXELS: " + + rootHints.getInt(MediaKeys.EXTRA_MEDIA_ART_SIZE_HINT_PIXELS, 0)); return mRoot; } @@ -128,6 +161,14 @@ public class TmaBrowser extends MediaBrowserServiceCompat { 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)) { + TmaMediaItem queue = mLibrary.getRoot(LEAF_CHILDREN); + if (queue != null) { + mSession.setQueue(queue.buildQueue()); + mPlayer.prepareMediaItem(queue.getPlayableByIndex(0)); + } + } } @Override diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaLibrary.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaLibrary.java index eb77019..8bda24d 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaLibrary.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaLibrary.java @@ -48,6 +48,8 @@ class TmaLibrary { mLoader = loader; 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"); diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaEvent.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaEvent.java index f6ec8af..491f940 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaEvent.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaEvent.java @@ -53,7 +53,7 @@ public class TmaMediaEvent { public static final TmaMediaEvent INSTANT_PLAYBACK = new TmaMediaEvent(EventState.PLAYING, StateErrorCode.UNKNOWN_ERROR, null, null, - ResolutionIntent.NONE, 0, null); + ResolutionIntent.NONE, Action.NONE, 0, null); /** The name of each entry is the value used in the json file. */ public enum EventState { @@ -105,23 +105,31 @@ public class TmaMediaEvent { PREFS } + /** The name of each entry is the value used in the json file. */ + public enum Action { + NONE, + RESET_METADATA + } + final EventState mState; final StateErrorCode mErrorCode; final String mErrorMessage; final String mActionLabel; final ResolutionIntent mResolutionIntent; + final Action mAction; /** How long to wait before sending the event to the app. */ final int mPostDelayMs; private final String mExceptionClass; public TmaMediaEvent(EventState state, StateErrorCode errorCode, String errorMessage, - String actionLabel, ResolutionIntent resolutionIntent, int postDelayMs, + String actionLabel, ResolutionIntent resolutionIntent, Action action, int postDelayMs, String exceptionClass) { mState = state; mErrorCode = errorCode; mErrorMessage = errorMessage; mActionLabel = actionLabel; mResolutionIntent = resolutionIntent; + mAction = action; mPostDelayMs = postDelayMs; mExceptionClass = exceptionClass; } @@ -152,6 +160,7 @@ public class TmaMediaEvent { ", mErrorMessage='" + mErrorMessage + '\'' + ", mActionLabel='" + mActionLabel + '\'' + ", mResolutionIntent=" + mResolutionIntent + + ", mAction=" + mAction + ", mPostDelayMs=" + mPostDelayMs + ", mExceptionClass=" + mExceptionClass + '}'; diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaItem.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaItem.java index 591f4cf..f79e273 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaItem.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaItem.java @@ -22,11 +22,6 @@ import static android.support.v4.media.MediaBrowserCompat.MediaItem.FLAG_PLAYABL import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_DURATION; import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_MEDIA_ID; -import static com.android.car.media.common.MediaConstants.CONTENT_STYLE_BROWSABLE_HINT; -import static com.android.car.media.common.MediaConstants.CONTENT_STYLE_GRID_ITEM_HINT_VALUE; -import static com.android.car.media.common.MediaConstants.CONTENT_STYLE_LIST_ITEM_HINT_VALUE; -import static com.android.car.media.common.MediaConstants.CONTENT_STYLE_PLAYABLE_HINT; - import android.os.Bundle; import android.support.v4.media.MediaBrowserCompat.MediaItem; import android.support.v4.media.MediaDescriptionCompat; @@ -46,8 +41,8 @@ public class TmaMediaItem { /** The name of each entry is the value used in the json file. */ public enum ContentStyle { NONE (0), - LIST (CONTENT_STYLE_LIST_ITEM_HINT_VALUE), - GRID (CONTENT_STYLE_GRID_ITEM_HINT_VALUE); + LIST (MediaKeys.CONTENT_STYLE_LIST_ITEM_HINT_VALUE), + GRID (MediaKeys.CONTENT_STYLE_GRID_ITEM_HINT_VALUE); final int mBundleValue; ContentStyle(int value) { mBundleValue = value; @@ -123,7 +118,11 @@ public class TmaMediaItem { return mParent; } + @Nullable TmaMediaItem getPlayableByIndex(long index) { + if (index < 0 || index >= mPlayableChildren.size()) { + return null; + } return mPlayableChildren.get((int)index); } @@ -208,8 +207,8 @@ public class TmaMediaItem { extras.putAll(metadataDescription.getExtras()); } - extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, mPlayableStyle.mBundleValue); - extras.putInt(CONTENT_STYLE_BROWSABLE_HINT, mBrowsableStyle.mBundleValue); + extras.putInt(MediaKeys.CONTENT_STYLE_PLAYABLE_HINT, mPlayableStyle.mBundleValue); + extras.putInt(MediaKeys.CONTENT_STYLE_BROWSABLE_HINT, mBrowsableStyle.mBundleValue); bob.setExtras(extras); return bob.build(); diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java index dc368ea..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.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 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; @@ -28,9 +29,6 @@ import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_T import static android.support.v4.media.session.PlaybackStateCompat.ERROR_CODE_APP_ERROR; import static android.support.v4.media.session.PlaybackStateCompat.STATE_ERROR; -import static com.android.car.media.common.MediaConstants.ERROR_RESOLUTION_ACTION_INTENT; -import static com.android.car.media.common.MediaConstants.ERROR_RESOLUTION_ACTION_LABEL; - import androidx.annotation.Nullable; import android.app.PendingIntent; import android.content.Context; @@ -44,6 +42,7 @@ import android.support.v4.media.session.PlaybackStateCompat; import android.util.Log; import android.widget.Toast; +import com.android.car.media.testmediaapp.TmaMediaEvent.Action; import com.android.car.media.testmediaapp.TmaMediaEvent.EventState; import com.android.car.media.testmediaapp.TmaMediaEvent.ResolutionIntent; import com.android.car.media.testmediaapp.TmaMediaItem.TmaCustomAction; @@ -107,8 +106,8 @@ public class TmaPlayer extends MediaSessionCompat.Callback { PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, prefsIntent, 0); Bundle extras = new Bundle(); - extras.putString(ERROR_RESOLUTION_ACTION_LABEL, event.mActionLabel); - extras.putParcelable(ERROR_RESOLUTION_ACTION_INTENT, pendingIntent); + extras.putString(MediaKeys.ERROR_RESOLUTION_ACTION_LABEL, event.mActionLabel); + extras.putParcelable(MediaKeys.ERROR_RESOLUTION_ACTION_INTENT, pendingIntent); state.setExtras(extras); } @@ -145,6 +144,46 @@ public class TmaPlayer extends MediaSessionCompat.Callback { } @Override + public void onPrepareFromMediaId(String mediaId, Bundle extras) { + super.onPrepareFromMediaId(mediaId, extras); + + TmaMediaItem item = mLibrary.getMediaItemById(mediaId); + prepareMediaItem(item); + } + + @Override + public void onPrepare() { + super.onPrepare(); + if (!mSession.isActive()) { + mSession.setActive(true); + } + // Prepare the first playable item (at root level) as the active item + if (mActiveItem == null) { + TmaMediaItem root = mLibrary.getRoot(mPrefs.mRootNodeType.getValue()); + if (root != null) { + prepareMediaItem(root.getPlayableByIndex(0)); + } + } + } + + void prepareMediaItem(@Nullable TmaMediaItem item) { + if (item != null && item.getParent() != null) { + if (mIsPlaying) { + stopPlayback(); + } + mActiveItem = item; + mActiveItem.updateSessionMetadata(mSession); + mSession.setQueue(item.getParent().buildQueue()); + + PlaybackStateCompat.Builder state = new PlaybackStateCompat.Builder() + .setState(PlaybackStateCompat.STATE_PAUSED, mCurrentPositionMs, mPlaybackSpeed) + .setActions(addActions(ACTION_PLAY)); + setActiveItemState(state); + mSession.setPlaybackState(state.build()); + } + } + + @Override public void onSkipToQueueItem(long id) { super.onSkipToQueueItem(id); if (mActiveItem != null && mActiveItem.getParent() != null) { @@ -232,6 +271,8 @@ public class TmaPlayer extends MediaSessionCompat.Callback { TmaAccountType.PAID.equals(mPrefs.mAccountType.getValue())) { Log.i(TAG, "Ignoring even for paid account"); return; + } else if (Action.RESET_METADATA.equals(event.mAction)) { + mSession.setMetadata(mSession.getController().getMetadata()); } else { setPlaybackState(event); } @@ -305,7 +346,8 @@ public class TmaPlayer extends MediaSessionCompat.Callback { } 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/TmaAssetProvider.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPublicProvider.java index fc9fd49..e7eb31b 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaAssetProvider.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPublicProvider.java @@ -21,56 +21,105 @@ 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 TmaAssetProvider extends ContentProvider { +public class TmaPublicProvider extends ContentProvider { private static final String TAG = "TmaAssetProvider"; - private static final String PACKAGE_NAME = "com.android.car.media.testmediaapp"; + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; - private static final String ASSET_URI_PREFIX = - ContentResolver.SCHEME_CONTENT + "://" + PACKAGE_NAME + ".assets/"; + 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 + "://" + PACKAGE_NAME + "/"; + ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + AUTHORITY + "/"; public static String buildUriString(String localArt) { - String prefix = localArt.startsWith("drawable") ? RESOURCE_URI_PREFIX : ASSET_URI_PREFIX; + 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 { - Log.i(TAG, "TmaAssetProvider#openAssetFile " + uri); + 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) { } - 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); + 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( diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaEventReader.java b/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaEventReader.java index c2e573a..2222afa 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaEventReader.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaEventReader.java @@ -26,6 +26,7 @@ import android.util.Log; import androidx.annotation.Nullable; import com.android.car.media.testmediaapp.TmaMediaEvent; +import com.android.car.media.testmediaapp.TmaMediaEvent.Action; import com.android.car.media.testmediaapp.TmaMediaEvent.EventState; import com.android.car.media.testmediaapp.TmaMediaEvent.ResolutionIntent; import com.android.car.media.testmediaapp.TmaMediaEvent.StateErrorCode; @@ -53,6 +54,7 @@ class TmaMediaEventReader { ERROR_MESSAGE, ACTION_LABEL, INTENT, + ACTION, /** How long to wait before sending the event to the app. */ POST_DELAY_MS, THROW_EXCEPTION @@ -70,11 +72,13 @@ class TmaMediaEventReader { private final Map<String, EventState> mEventStates; private final Map<String, StateErrorCode> mErrorCodes; private final Map<String, ResolutionIntent> mResolutionIntents; + private final Map<String, Action> mActions; private TmaMediaEventReader() { mEventStates = enumNamesToValues(EventState.values()); mErrorCodes = enumNamesToValues(StateErrorCode.values()); mResolutionIntents = enumNamesToValues(ResolutionIntent.values()); + mActions = enumNamesToValues(Action.values()); } @Nullable @@ -86,6 +90,7 @@ class TmaMediaEventReader { getString(json, Keys.ERROR_MESSAGE), getString(json, Keys.ACTION_LABEL), getEnum(json, Keys.INTENT, mResolutionIntents, ResolutionIntent.NONE), + getEnum(json, Keys.ACTION, mActions, Action.NONE), getInt(json, Keys.POST_DELAY_MS), getString(json, Keys.THROW_EXCEPTION)); } 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 95f8f89..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,12 +53,13 @@ import static com.android.car.media.testmediaapp.loader.TmaLoaderUtils.enumNames 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; import java.util.EnumSet; +import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -140,7 +141,9 @@ class TmaMediaMetadataReader { MediaMetadataCompat fromJson(JSONObject object) throws JSONException { MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); - for (String jsonKey : object.keySet()) { + Iterator<String> keys = object.keys(); + while (keys.hasNext()) { + String jsonKey = keys.next(); MetadataKey key = mMetadataKeys.get(jsonKey); if (key != null) { switch (key.mKeyType) { @@ -150,7 +153,7 @@ class TmaMediaMetadataReader { 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/prefs/TmaEnumPrefs.java b/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaEnumPrefs.java index ad870b6..347e52c 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaEnumPrefs.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaEnumPrefs.java @@ -83,6 +83,8 @@ public class TmaEnumPrefs { public enum TmaBrowseNodeType implements EnumPrefValue { 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"); @@ -104,6 +106,29 @@ public class TmaEnumPrefs { } } + /* To simulate the events order after login. Media apps should update playback state first, then + * load the browse tree. But sometims some apps (e.g., GPB) don't follow this order strictly. */ + public enum TmaLoginEventOrder implements EnumPrefValue { + PLAYBACK_STATE_UPDATE_FIRST("Update playback state first", "state-first"), + BROWSE_TREE_LOAD_FRIST("Load browse tree first", "tree-first"); + + private final PrefValueImpl mPrefValue; + + TmaLoginEventOrder(String displayTitle, String id) { + mPrefValue = new PrefValueImpl(displayTitle, id); + } + + @Override + public String getTitle() { + return mPrefValue.getTitle(); + } + + @Override + public String getId() { + return mPrefValue.getId(); + } + } + private static class PrefValueImpl implements EnumPrefValue { private final String mDisplayTitle; diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefs.java b/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefs.java index 3305a06..8e9d89f 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefs.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefs.java @@ -24,6 +24,7 @@ import androidx.preference.PreferenceManager; import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaAccountType; import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaBrowseNodeType; +import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaLoginEventOrder; import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaReplyDelay; import java.util.HashMap; @@ -45,6 +46,9 @@ public class TmaPrefs { /** Wait time for openAssetFile. */ public final PrefEntry<TmaReplyDelay> mAssetReplyDelay; + /** Media apps event (update playback state, load browse tree) order after login. */ + public final PrefEntry<TmaLoginEventOrder> mLoginEventOrder; + public synchronized static TmaPrefs getInstance(Context context) { if (sPrefs == null) { @@ -62,7 +66,8 @@ public class TmaPrefs { ACCOUNT_TYPE_KEY, ROOT_NODE_TYPE_KEY, ROOT_REPLY_DELAY_KEY, - ASSET_REPLY_DELAY_KEY + ASSET_REPLY_DELAY_KEY, + LOGIN_EVENT_ORDER_KEY } /** @@ -128,6 +133,9 @@ public class TmaPrefs { mAssetReplyDelay = new EnumPrefEntry<>(TmaPrefKey.ASSET_REPLY_DELAY_KEY, TmaReplyDelay.values(), TmaReplyDelay.NONE); + + mLoginEventOrder = new EnumPrefEntry<>(TmaPrefKey.LOGIN_EVENT_ORDER_KEY, + TmaLoginEventOrder.values(), TmaLoginEventOrder.PLAYBACK_STATE_UPDATE_FIRST); } diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefsFragment.java b/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefsFragment.java index 482de16..066cc9c 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefsFragment.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefsFragment.java @@ -26,6 +26,7 @@ import androidx.preference.PreferenceScreen; import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaAccountType; import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaBrowseNodeType; +import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaLoginEventOrder; import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaReplyDelay; import com.android.car.media.testmediaapp.prefs.TmaPrefs.PrefEntry; @@ -46,6 +47,8 @@ public class TmaPrefsFragment extends PreferenceFragmentCompat { TmaReplyDelay.values())); screen.addPreference(createEnumPref(context, "Asset delay: random value in [v, 2v]", prefs.mAssetReplyDelay, TmaReplyDelay.values())); + screen.addPreference(createEnumPref(context, "Login event order", prefs.mLoginEventOrder, + TmaLoginEventOrder.values())); setPreferenceScreen(screen); } diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..df49ba3 --- /dev/null +++ b/build.gradle @@ -0,0 +1,41 @@ +/* + * 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. + */ + +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar Binary files differnew file mode 100644 index 0000000..f6b961f --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ce751bb --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Sep 26 14:52:51 PDT 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/read-me.txt b/read-me.txt deleted file mode 100644 index a83c3e5..0000000 --- a/read-me.txt +++ /dev/null @@ -1 +0,0 @@ -This repository is only for test applications. diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..f3b7ee8 --- /dev/null +++ b/readme.md @@ -0,0 +1,49 @@ +# Car test apps + +This repository is only for car test applications. + +## Building + +If you are not contributing to the repo, you can clone the repo via `git clone sso://googleplex-android/platform/packages/apps/Car/tests --branch pi-car-dev --single-branch`. Otherwise, see [workstation setup](#workstation-setup). + +Install [Android Studio](go/install-android-studio). Then import the `tests` Gradle project into Android Studio. + +### TestMediaApp + +TestMediaApp should be one of the run configurations. The green Run button should build and install the app on your phone. + +To see TestMediaApp in Android Auto Projected: + +1. Open Android Auto on phone +2. Click hamburger icon at top left -> Settings +3. Scroll to Version at bottom and tap ~10 times to unlock Developer Mode +4. Click kebab icon at top right -> Developer settings +5. Scroll to bottom and enable "Unknown sources" +6. Exit and re-open Android Auto +7. TestMediaApp should now be visible (click headphones icon in phone app to see app picker) + +## Contributing + +### Workstation setup + +Install [repo](https://source.android.com/setup/build/downloading#installing-repo) command line tool. Then run: + +``` +sudo apt-get install gitk +sudo apt-get install git-gui +mkdir WORKING_DIRECTORY_FOR_GIT_REPO +cd WORKING_DIRECTORY_FOR_GIT_REPO +repo init -u persistent-https://googleplex-android.git.corp.google.com/platform/manifest -b pi-car-dev -g name:platform/tools/repohooks,name:platform/packages/apps/Car/tests --depth=1 +repo sync +``` + +### Making a change + +``` +repo start BRANCH_NAME . +# Make some changes +git gui & +# Use GUI to create a CL. Check amend box to update a work-in-progress CL +repo upload . +``` + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..38f6519 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,17 @@ +/* + * 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. + */ + +include ':TestMediaApp' |